Loading imageviews with AFNetworking via JSON hierarchy and objectForKey - ios

Very new to AFNetworking, but after many SO questions... I've concluded that this is the solution to my issue.
I've got a hierarchy of JSON data loading my TableViewController cells dynamically. All of my text strings are loading appropriately but the image from my URL is not making it to my imageview.
Before utilizing the AFNetworking library, I had the images loaded, but I was experiencing some issues with the photos not staying loaded when I scrolled away and returned (they had to load again.)
What am I missing to get my images in there?
TableViewController.m
-(void)viewDidLoad {
[super viewDidLoad];
// Set the side bar button action. When it's tapped, it'll show up the sidebar.
siderbarButton.target = self.revealViewController;
siderbarButton.action = #selector(revealToggle:);
// Set the gesture
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
self.tableView.backgroundColor = [UIColor whiteColor];
self.parentViewController.view.backgroundColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1];
self.navigationItem.titleView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"logo_app_white.png"]];
NSURL *myURL = [[NSURL alloc]initWithString:#"http://domain.com/json2.php"];
NSData *myData = [[NSData alloc]initWithContentsOfURL:myURL];
NSError *error;
jsonArray = [NSJSONSerialization JSONObjectWithData:myData options:NSJSONReadingMutableContainers error:&error];
[tableView reloadData]; // if tableView is unidentified make the tableView IBOutlet
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return jsonArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NeedCardTableViewCell *cell = (NeedCardTableViewCell *) [tableView dequeueReusableCellWithIdentifier:#"needCard"];
if (cell == nil)
{
cell = [[NeedCardTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"needCard"];
}
NSDictionary *needs = jsonArray[indexPath.row]; // get the data dict for the row
cell.textNeedTitle.text = [needs objectForKey: #"needTitle"];
cell.textNeedPoster.text = [needs objectForKey: #"needPoster"];
cell.textNeedDescrip.text = [needs objectForKey: #"needDescrip"];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"userImage" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
_imageProfPic.image = responseObject;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
return cell;
}

Have you tried using the AFNetworking+UIImageView category? You can then call:
[_imageProfPic setImage:[NSURL URLWithString:#"http://myimageurl.com/imagename.jpg"]];
This will make a request and then set the returned image to your UIImageView's UIImage without you having to do anything else. You should also consider initializing NSURLCache in your AppDelegate:
NSURLCache *cache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
diskCapacity:10 * 1024 * 1024
diskPath:nil];
[NSURLCache setSharedURLCache:cache];
Take a look at NSHipster's run down on NSURLCache. This will help reload images, and all your requests, much faster the second time around. This is increasingly important when dealing with images and tables.

Manage to figure this one out with the use of this tutorial:
Networking Made Easy with AFNetworking
I used the final snippet of code to get my desired result:
NSURL *url = [[NSURL alloc] initWithString:[movie objectForKey:#"artworkUrl100"]];
[cell.imageView setImageWithURL:url placeholderImage:[UIImage imageNamed:#"placeholder"]];
I cut the second line of his code because I already have an if statement in my PHP/JSON
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NeedCardTableViewCell *cell = (NeedCardTableViewCell *) [tableView dequeueReusableCellWithIdentifier:#"needCard"];
if (cell == nil)
{
cell = [[NeedCardTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"needCard"];
}
NSDictionary *needs = jsonArray[indexPath.row]; // get the data dict for the row
cell.textNeedTitle.text = [needs objectForKey: #"needTitle"];
cell.textNeedPoster.text = [needs objectForKey: #"needPoster"];
cell.textNeedDescrip.text = [needs objectForKey: #"needDescrip"];
NSURL *url = [[NSURL alloc] initWithString:[needs objectForKey:#"userImage"]];
[cell.imageProfPic setImageWithURL:url];
return cell;
}
It worked like a charm, and the tutorial was pretty helpful since I'm a rookie with AFNetworking.

Related

UICollectionView will not display cells in custom popupview

I have a UICollectionView inside a popupview which is just a simple view in a xib file. In the same xibfile I have my UICollectionViewCell like this:
The View is connected to SampleViewController class and the UICollectionViewCell is connected to PekProfil class
The code I use for displaying the cells is
[_collectionView registerClass:[PekProfil class] forCellWithReuseIdentifier:#"vinnare"];
_collectionView.delegate = self;
_collectionView.dataSource = self;
and:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"vinnare";
PekProfil * cellen = (PekProfil *)[collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
cellen.backgroundColor = [UIColor grayColor];
if (cellen==nil) {
cellen = [[PekProfil alloc] init];
}
NSString * iden = [_winners objectAtIndex:indexPath.row];
FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc]
initWithGraphPath:iden
parameters:nil
HTTPMethod:#"GET"];
[request startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection,
id result,
NSError *error) {
// Handle the result
NSLog(#"fetched friends:%#", result);
cellen.pekProfilNamn.text = [result objectForKey:#"name"];
NSString * ideno = [NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?width=640&height=640", iden];
NSLog(#"IDEN:%#", ideno);
NSString *pictureUrl = ideno;
NSURL * url = [NSURL URLWithString:pictureUrl];
NSLog(#"PICURL:%#", pictureUrl);
NSData *data = [NSData dataWithContentsOfURL:url];
NSLog(#"%#", data);
UIImage *img = [[UIImage alloc] initWithData:data];
//roundpic
CALayer *imageLayer = cellen.profilePicView.layer;
[imageLayer setCornerRadius:5];
[imageLayer setBorderWidth:1];
[imageLayer setMasksToBounds:YES];
[cellen.profilePicView.layer setCornerRadius:cellen.profilePicView.frame.size.width/2];
[cellen.profilePicView.layer setMasksToBounds:YES];
//END
cellen.profilePicView.image = img;
}];
return cellen;
}
And I know that the facebookcode works since I use the same code in other places.
However running this only displays this:
Any ideas why the content is not displaying?
You should create a custom subclass of UiCollectionViewCell. If you want to layout your cell in IB check include XIB file. It's not possible to combine classes like this in objective C. Register you nib your reuse identifier and make your image view and label public properties that you can then set in cellForItemAtIndexPath

Retrieving core data object from cell

I am saving an object from core data to a cell as listed bellow. The URL is being saved to the cell correctly and working great. My problem is that, when a user taps on a cell, I would like the URL that is saved to that cell to be passed to my detailedViewController for use. I have some code that I have tried but the url is nil in the detailedViewController. If you have any better way of accomplishing the same thing, that would be fine. The code is listed bellow -
Here is where I save it to the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
PhotoCell *cell = (PhotoCell *)[self.tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[PhotoCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
// Configure the cell...
FeedEntity *feed = [_fetchedResultsController objectAtIndexPath:indexPath];
NSData *data = feed.imageData;
self.feedImage = [UIImage imageWithData:data];
cell.thumbImage = self.feedImage;
NSData *stringData = feed.urlString;
self.stringForURL = [[NSString alloc] initWithData:stringData encoding:NSUTF8StringEncoding];
self.stringForURL = [self.stringForURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
self.finalURL = [NSURL URLWithString:self.stringForURL];
cell.finalURL = self.finalURL;
return cell;
}
Here is where I retrieve the url from the cell and pass it to the detailedViewController:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
// Code to create detailed view and set properties
self.detailsViewController = [[DetailsViewController alloc] init];
NSIndexPath *path = [self.tableView indexPathForSelectedRow];
FeedEntity *feed = [_fetchedResultsController objectAtIndexPath:path];
NSData *stringData = feed.urlString;
NSString *stringURL = [[NSString alloc] initWithData:stringData encoding:NSUTF8StringEncoding];
NSLog(#"Here is the string before: %#", stringURL);
stringURL = [self.stringForURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *urlForDetail = [NSURL URLWithString:self.stringForURL];
NSLog(#"Here is the url before it goes to the detailed: %#", urlForDetail);
self.detailsViewController.finalURL = urlForDetail;
[self.navigationController pushViewController:self.detailsViewController animated:YES];
}
Save the video (in didFinishPickingMediaWithInfo:):
self.videoURL = [info objectForKey:UIImagePickerControllerMediaURL];
NSData *videoData = [NSData dataWithContentsOfURL:self.videoURL];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingFormat:#"/vid1.mp4"];
self.urlForSave = [NSURL fileURLWithPath:path];
//Look at YES
[videoData writeToFile:path atomically:YES];
[self saveImageAndVideo];
Here is SaveVideoAndPhoto:
- (void)saveImageAndVideo {
NSManagedObjectContext *context = [self managedObjectContext];
FeedEntity *feedEntity = [NSEntityDescription insertNewObjectForEntityForName:#"FeedEntity" inManagedObjectContext:context];
NSData *imageData = UIImageJPEGRepresentation(self.thumbImage, 0.8f);
self.photoData = imageData;
NSString *stringForSave = [self.urlForSave absoluteString];
NSLog(#"URL before save: %#", stringForSave);
//NSData * stringData = [stringForSave dataUsingEncoding:NSUTF8StringEncoding];
[feedEntity setValue:imageData forKey:#"imageData"];
[feedEntity setValue:[NSDate date] forKey:#"timeStamp"];
[feedEntity setValue: stringForSave forKey:#"urlString"];
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
} else {
NSLog(#"URL's are being saved");
}
}
Your problem is in the code in cellForRowAtIndexPath:
self.finalURL = [NSURL URLWithString:self.stringForURL];
You are setting the URL as a property of SELF, which in this case is your viewController. You want to set it on the CELL. Change all that code when you create the cell from self.whatever to cell.whatever if you want to save them as properties of the cell. It might help you if you did some reading up on scope in objective-c.
Also, on a side note, there are a few things you are doing that are unnecessary. Firstly is this:
NSIndexPath *path = [self.tableView indexPathForSelectedRow];
You don't need to create an indexPath object inside this function, because you are already provided it by the function itself with the variable indexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Secondly, inside of didSelectRowAtIndexPath, if you want to get the url, you should be doing something like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// deselect the row if you want the cell to fade out automatically after tapping
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// get a reference to the cell that the user tapped
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
// get the url from the cell, assuming your cell has a property called finalURL on it that you set some value
// to originally when it was created
NSURL *url = cell.finalURL;
// do something with that URL here...
}
Keep in mind this is slightly unorthodox. You really should get the information from your tableView's datasource. If you have an array of objects that you are using to populate your tableView, it's probably a better idea to simply get the object itself from our array with the given indexPath rather than save the information on the cell as a property and access it that way. I would highly suggest watching some tutorial videos or do some reading up, preferably in the iOS docs themselves, to try to learn best practices for UITableViews.

How make parse image from JSON

I have some project where i must paste image from JSON. I try to find any tutorial about this, try to see any video from this theme but nothing. So my problem have:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSDictionary *allDataDictionary = [NSJSONSerialization JSONObjectWithData:webdata options:0 error:nil];
NSDictionary *playlist =[allDataDictionary objectForKey:#"playlist_data"];
for (NSDictionary *diction in playlist) {
NSString *name = [diction objectForKey:#"text1"];
NSString *namesong = [diction objectForKey:#"text2"];
NSString *images = [diction objectForKey:#"image"];
NSLog(#"%#", images);
[array addObject:text1];
[array2 addObject:text2];
}
[[self tableTrack]reloadData];
}
I added text 1 to cell also text 2 its work perfect but how to add image to to array 3(image in cell in my tableView)?
I tried also to added for image but its not work for me:
NSURL *imageURL = [NSURL URLWithString:[appsdict objectForKey:#"image"]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *imageLoad = [[UIImage alloc] initWithData:imageData];
cell.imageView.image = imageLoad;
Please help with my problem or maybe give some tutorial with parse image from JSON, also youtube haven't perfect tutorial from JSON parse. Thanks!
Don't keep multiple data source like array1, array2, array3 etc. It is not good coding and will confuse while debugging/fixing issues. Instead maintain single array with information for displaying in individual cell of the table view.
for (NSDictionary *dict in playlist) {
// array is single data source
[array addObject:diction];
}
Then while assigning data to the table view cell use,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier] autorelease];
}
cell.text1 = [[array objectAtIndex:indexPath.row] objectForKey:#"text1"];
cell.text2 = [[array objectAtIndex:indexPath.row] objectForKey:#"text2"];
//For displaying image by lazy loading technique use SDWebImage.
[cell.imageView setImageWithURL:[NSURL URLWithString:[[array objectAtIndex:indexPath.row] objectForKey:#"image"]] placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
// If no place holder image, use this way
// [cell.imageView setImageWithURL:[NSURL URLWithString:[[array objectAtIndex:indexPath.row] objectForKey:#"image"]] placeholderImage:nil];
}
Like I mentioned in comments for lazy loading images from the URLs in JSON response, use SDWebImage. Usage is simple like I have shown above. Alternately you can implement lazy loading yourself by studying this sample code from Apple: LazyTableImages
Hope that helps!
According to https://developer.apple.com/library/mac/documentation/Foundation/Reference/NSJSONSerialization_Class/Reference/Reference.html and http://www.json.org/ you can't keep binary data in JSON (unless it is in base64).
It seems there's a problem with your webdata which is not a valid JSON. Did you check what is set under #"image" key in that dictionary while debugging? You can use [NSJSONSerialization isValidJSONObject:webdata] as well to see if your data is ok.
//prepare your method here..
-(void)hitimageurl{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:replacephotostring]];
//set your image on main thread.
dispatch_async(dispatch_get_main_queue(), ^{
if (_photosArray==nil) {
_showImageView.image = [UIImage imageNamed:#"noimg.png"];
} else {
[_showImageView setImage:[UIImage imageWithData:data]];
}
});
});

Why is my UITabeViewContent reorders everytime I refresh it?

i have a UITebleView with costume UITableViewCells. Every time I refresh the them the content is reordering itself. anyone know why?
I am fatching the data from a JSON, I dont do some sort of sorting, I just display the data acording to the TableViewCell indexpath.row
And this is the code I set the UITableViewCell content:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *costumeCell = #"Cell";
StoreCell *cell = [tableView dequeueReusableCellWithIdentifier:costumeCell];
if (!cell) {
cell = [[StoreCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:costumeCell];
}
NSDictionary *dict;
dict = [application objectAtIndex:indexPath.row];
[downloadQueue addOperationWithBlock:^{
name = [dict objectForKey:#"name"];
detileName = [dict objectForKey:#"detailName"];
itmsLink = [dict objectForKey:#"itms-serviceLink"];
icon = [dict objectForKey:#"icon"];
developer = [dict objectForKey:#"developer"];
version = [dict objectForKey:#"version"];
category = [dict objectForKey:#"category"];
rating = [dict objectForKey:#"rating"];
ratingNumbers = [dict objectForKey:#"ratingNumber"];
description = [dict objectForKey:#"description"];
developerEmails = [dict objectForKey:#"developerEmail"];
cell.AppName.text = name;
cell.category.text = category;
cell.rater.text = [NSString stringWithFormat:#"(%#)", ratingNumbers];
if ([rating intValue] == 1) {
cell.rating.image = [UIImage imageNamed:#"1.png"];
}
if ([rating intValue] == 2) {
cell.rating.image = [UIImage imageNamed:#"2.png"];
}
if ([rating intValue] == 3) {
cell.rating.image = [UIImage imageNamed:#"3.png"];
}
if ([rating intValue] == 4) {
cell.rating.image = [UIImage imageNamed:#"4.png"];
}
cell.itms = itmsLink;
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:icon]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse* response, NSData* data, NSError* error){
if(error)
{
// Error Downloading image data
cell.AppIcon.image = [UIImage imageNamed:#"placeholder.png"];
}
else
{
[cell.AppIcon setImage:[UIImage imageWithData:data]];
}
}];
cell.AppIcon.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:icon]]];
cell.number.text = [NSString stringWithFormat:#"%li", (long)indexPath.row + 1];
}];
cell.AppIcon.layer.masksToBounds = YES;
cell.AppIcon.layer.cornerRadius = 16.0;
cell.installButton.layer.masksToBounds = YES;
cell.installButton.layer.cornerRadius = 5.0f;
cell.installButton.layer.borderColor = [UIColor darkGrayColor].CGColor;
cell.installButton.layer.borderWidth = 1.0f;
return cell;
}
Since you're fetching from JSON, the server you got it from may not sort your data for you. Course its impossible for us to know what its doing without you divulging into the the server's APIs.
What you can do, without giving further info is just do a sort after you receive new data from the server:
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortedApplicationArray = [application sortedArrayUsingDescriptors:#[descriptor]];
application = sortedApplicationArray;
Just don't put that code inside your cellForRowAtIndexPath, cos it'll do a sort each time a row is created!
I think I know what the problem is.
You have an asynchronous operation in your cellForRowAtIndexPath. You should set the cell UI elements directly with the dict object and outside the operation queue.
The only thing that looks like it needs to be done asynchronously is the image download. I recommend you use SDWebImage from github for that, it handles the download in its own queue and caches the image in memory and on disk.

SIGABRT accessing NSArray with indexPath.row

I am working on an app for a game server company and part of the app requires the user to see a list of his or her game servers and whether or not they are online, offline, how many players on them, the server name, etc. This data is all found in a PHP file hosted on the web updated from a MySQL database which when viewed, outputs JSON.
Using the code below, this doesn't seem to work. I load the view and right away get a "Thread 1: signal SIGABRT" error on the line with NSDictionary *myServer = [servers objectAtIndex:indexPath.row];. When removing indexPath.row and replacing it with either a 0 or 1, the data is displayed on the UITableView in my Storyboard, except it is displayed 4 times in a row and only for that entry in the JSON file (either 0 or 1). I can't keep it at a fixed number as the client might have 100 servers, or just 5 servers which is why I need something like indexPath.row. Below, I also attached exactly what the JSON looks like when given from the server and accessed directly from the app's code
I'd really appreciate it if someone could please let me know what the problem is and propose a solution unique to my situation to get rid of this SIGABRT error and once we do, make sure it doesn't show 4 times in the TableView like it is now.
My header file:
#import <UIKit/UIKit.h>
#import "ServerDetailViewController.h"
#interface SecondViewController : UITableViewController {
IBOutlet UITableView *mainTableView;
NSDictionary *news;
NSMutableData *data;
}
#property (weak, nonatomic) IBOutlet UIBarButtonItem *refreshServersButton;
- (IBAction)refreshServers:(id)sender;
#end
My main file:
#import "SecondViewController.h"
#interface SecondViewController ()
#end
#implementation SecondViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURL *url = [NSURL URLWithString:#"REDACTED"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
data = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData
{
[data appendData:theData];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
news = [NSJSONSerialization JSONObjectWithData:data options:nil error:nil];
[mainTableView reloadData];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
UIAlertView *errorView = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Unable to load server list. Make sure you are connect to either 3G or Wi-Fi or try again later." delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil, nil];
[errorView show];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (int)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [news count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UIColor *colorGreen = [UIColor colorWithRed:91.0f/255.0f green:170.0f/255.0f blue:101.0f/255.0f alpha:1.0f];
UIColor *colorRed = [UIColor redColor];
static NSString *CellIdentifier = #"MainCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
UILabel *serverName = (UILabel *)[cell viewWithTag:100];
UILabel *serverPlayers = (UILabel *)[cell viewWithTag:101];
UILabel *serverStatus = (UILabel *)[cell viewWithTag:102];
UILabel *serverOfflineName = (UILabel *)[cell viewWithTag:103];
serverPlayers.textColor = [UIColor grayColor];
NSDictionary *resultDict = [news objectForKey:#"result"];
NSArray *servers = [resultDict objectForKey:#"servers"];
NSDictionary *myServer = [servers objectAtIndex:indexPath.row];
NSString *titleOfServer = [myServer objectForKey:#"title"];
NSNumber *statusOfServer = [NSNumber numberWithInt:[[myServer objectForKey:#"status"] intValue]];
NSNumber *playersOnServer = [NSNumber numberWithInt:[[myServer objectForKey:#"players"] intValue]];
if ([[statusOfServer stringValue] isEqualToString:#"0"]) {
serverName.text = #"";
serverOfflineName.text = titleOfServer;
serverStatus.textColor = colorRed;
serverStatus.text = #"OFFLINE";
serverPlayers.text = #"";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
} else if ([[statusOfServer stringValue] isEqualToString:#"1"]) {
serverName.text = titleOfServer;
serverOfflineName.text = #"";
serverStatus.textColor = colorGreen;
serverStatus.text = #"ONLINE";
serverPlayers.text = [playersOnServer stringValue];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
} else if ([[statusOfServer stringValue] isEqualToString:#"2"]) {
serverName.text = #"";
serverOfflineName.text = titleOfServer;
serverStatus.textColor = [UIColor blueColor];
serverStatus.text = #"BUSY";
serverPlayers.text = #"";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
} else if ([[statusOfServer stringValue] isEqualToString:#"3"]) {
serverName.text = #"";
serverOfflineName.text = titleOfServer;
serverStatus.textColor = [UIColor grayColor];
serverStatus.text = #"SUSPENDED";
serverPlayers.text = #"";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
} else if ([[statusOfServer stringValue] isEqualToString:#"-1"]) {
serverName.text = #"";
serverOfflineName.text = titleOfServer;
serverStatus.textColor = [UIColor orangeColor];
serverStatus.text = #"CRITICAL ERROR";
serverPlayers.text = #"";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ServerDetailViewController *detail = [self.storyboard instantiateViewControllerWithIdentifier:#"detail"];
[self.navigationController pushViewController:detail animated:YES];
}
- (IBAction)refreshServers:(id)sender {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURL *url = [NSURL URLWithString:#"REDACTED"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
#end
JSON code from server: {"status":"OK","error":"","debug":"2 server(s)","result":{"servers":[{"id":1,"title":"Test","players":0,"slots":10,"status":3},{"id":2,"title":"Creative Spawn","players":0,"slots":5,"status":-1}]}}
From your code, this looks like the source of error.(However, I didn't read the whole thing.)
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [news count]; //counted number of items in your whole json object.
}
and in your cellForRowAtIndexPath:(NSIndexPath *)indexPath
NSDictionary *resultDict = [news objectForKey:#"result"];
NSArray *servers = [resultDict objectForKey:#"servers"];
// you used a different array(an item of the whole json array).
// Since news object has more items than servers, it caused an out of bound here.
NSDictionary *myServer = [servers objectAtIndex:indexPath.row];
Try doing following to your code
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSDictionary *resultDict = [news objectForKey:#"result"];
NSArray *servers = [resultDict objectForKey:#"servers"];
return [servers count]; //counted number of items in your whole json object.
}
TL;DR
The reason for the crash, you're using the news.count as the number of rows in the table, yet referencing the indexPath.row in the servers array (which it isn't guaranteed to be).
There's a few things here:
Firstly, this is not a very proficient way of networking, since you support iOS5, I would suggest using the following method (or something similar) :
[NSURLConnection sendAsynchronousRequest:theRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSString *dataString = [[NSString alloc] initWithBytes:[data bytes] length:[[data bytes] length] encoding:NSUTF8StringEncoding];
}];
Secondly, I would strongly recommend the MVC model, managing data is not something for a controller - as someone has mentioned, the code at the moment isn't as easy reading as it could be (as well as maintaining it!).
Thirdly, I would recommend you employ more defensive coding, below are the points I've spotted while reading through:
news, well rather [NSJSONSerialization JSONObjectWithData:data options:nil error:nil];,, isn't guaranteed to return a dictionary at all; although it is treated as such
The reason for the crash, you're using the news.count as the number of rows in the table, yet referencing the indexPath.row in the servers array (which it isn't guaranteed to be).
If I were you, I'd probably start with simplifying the networking, followed by created a simple model (for example Server), have the model parse the JSON that's relevant to it. I'd go so far as to include a static method in your Server model, say 'retrieveServers', that returns an NSArray of Server objects.
That way, all your controller is doing is:
[self setNews:[Server retrieveServers]];
[_tableView reloadData];
Rather than having a lot of irrelevant code in your controller - this will increase maintainability and readability.
If you were so inclined, you could take another step, and provide custom accessors, rather than referencing the members directly via the model, for example:
Server *currentServer = nil;
if( self.news.count > indexPath.row ) {
currentServer = [_news objectAtIndex:indexPath.row];
}
[serverPlayers setText:(currentServer ? [currentServer getPlayers] : [Server defaultPlayerValue]]
The code above is being safe with checking that the array has at least the same number of elements in it as we need, and secondly, when assigning the value to the table cell (which may be re-used, and so needs to be set to sane values for every possible branch of execution). The advantages of doing the above: readability, centralised default value, maintainability.
I apologise if this seems rather overkill (TL;DR added ;/), trying to provide some pointers, which would aid in debugging if you see no other reason to employ the tips above.

Resources