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.
Related
I am trying to to pretty standard operation, which is basically, getting all records form my local SQLITE database, then getting additional data form web, merge this data (which is NSMutableArray) and display it in table view. In viewDidLoad, array has all required elements, but in numberOfRowsInSection: it equals to nil. Because of this, I cannot display items in tableview. So where could it get set to nil? Thank you for any help.
Code for InboxViewControler.m
//
// ArticleViewController.m
// ReadLater
//
// Created by Ibragim Gapuraev on 09/06/2014.
// Copyright (c) 2014 Sermilion. All rights reserved.
//
#import "InboxViewController.h"
#import "LoginViewController.h"
#import "SHCTableViewCell.h"
#interface InboxViewController ()
#end
#implementation InboxViewController
#synthesize db, articles, response, jsonData;
- (NSMutableArray* ) articles
{
if (!articles) {
articles = [[NSMutableArray alloc] initWithCapacity:20];
}
return articles;
}
- (Database* ) db
{
if (!db) {
db = [[Database alloc] init];
}
return db;
}
//---------------------------------Getting data from web-------------------------------------------//
/**
The method will connect to a given url and send date of article that has been added at last.
Then, connectionDidFinishLoading will receive json array of all articles, that have been added to server database after that time
**/
#pragma mark Connection to server
- (void) makeConnetion:(id)data
{
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://localhost/nextril/index.php"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15.0];
NSURLConnection * connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
//send id of article that was added last, to server,
//which will return json arrya of all articles with id greater then the one sent
[request setHTTPMethod:#"POST"];
[request setHTTPBody:[data dataUsingEncoding:NSUTF8StringEncoding]];
if (connection) {
NSLog(#"viewWillAppear: Connecting to server to get data...");
}else{
NSLog(#"viewWillAppear: Error while connecting...");
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData*)data
{
response = [[NSData alloc] initWithData:data];
}
//Check if data been received
- (void) connectionDidFinishLoading:(NSURLConnection *)connection
{
if(sizeof(response)>0){
//NSLog(#"Got response from server %#", response);
NSError* error;
NSArray* json = [NSJSONSerialization
JSONObjectWithData:response //1
options:kNilOptions
error:&error];
self.jsonData = [[NSMutableArray alloc] initWithArray:json];
int count = 0;
[self.db openDatabase];
BOOL added = false;
BOOL addedToUser = false;
NSLog(#"jsonData %d", jsonData.count);
for (int i=0; i<self.jsonData.count; i++) {
NSDictionary *item = [self.jsonData objectAtIndex:i];
NSString* content = [item objectForKey:#"content"];
NSString* author = [item objectForKey:#"author"];
NSString* date = [item objectForKey:#"date"];
NSString* url = [item objectForKey:#"url"];
NSString* tags = [item objectForKey:#"tags"];
NSInteger archived = [[item objectForKey:#"archived"]integerValue];
NSString* title = [item objectForKey:#"title"];
//NSLog(#"",);
Article* article = [[Article alloc]initWithId:0 content:content author:author date:date url:url tags:tags arhived:archived title:title];
added = [self.db addArticleToArticleDB:article];
if (added == true) {
NSInteger last_id = [self.db getLastArticleID];
article.article_id = last_id;
[self.articles addObject:article];
addedToUser = [self.db addArticleToUserArticleDB:article];
}
count++;
}
if (added == true && addedToUser == true) {
NSLog(#"connectionDidFinishLoading: Articles has been imported. Size: %d %lu", jsonData.count, (unsigned long)jsonData.count);
}else{
NSLog(#"connectionDidFinishLoading: Failed to import article.");
}
NSArray *importedArticles = [self.db importAllArticlesForUser:16 archived:0];
[self.articles addObjectsFromArray:importedArticles];
[self.db closeDatabase];
}else{
NSLog(#"connectionDidFinishLoading: Did not get resopnse from server: %#", response);
}
connection = nil;
}
//----------------------------------------------------------------------------------------------------
#pragma mark TODO: work out why data from server loads only after second login
#pragma mark view
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.db openDatabase];
NSString* date_added = [self.db getLastArticleDate];
[self makeConnetion:(id)date_added];
NSLog(#"viewWillAppear: self.articles: %d", self.articles.count);
[self.db closeDatabase];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.dataSource = self;
self.tableView.delegate = self;
self.tableView.separatorColor = [UIColor clearColor];
self.tableView.backgroundColor = [UIColor blackColor];
[self.tableView registerClass:[SHCTableViewCell class] forCellReuseIdentifier:#"Content"];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
NSLog(#"numberOfRowsInSection: self.articles: %d", self.articles.count);
return self.articles.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Content";
SHCTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.textLabel.backgroundColor = [UIColor clearColor];
NSMutableArray* safeArticles = self.articles;
// Configure the cell...
Article* article = [safeArticles objectAtIndex:indexPath.row];
NSString *listingKey = article.title;
NSString *listingValues = article.url;
cell.textLabel.text = listingKey;
cell.detailTextLabel.text = listingValues ;
cell.delegate = self;
cell.todoItem = article;
return cell;
}
#pragma mark cell atributes
-(UIColor*)colorForIndex:(NSInteger) index {
NSUInteger itemCount = self.articles.count - 1;
float val = ((float)index / (float)itemCount) * 0.6;
return [UIColor colorWithRed: 1.0 green:val blue: 0.0 alpha:1.0];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 70.0f;
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
cell.backgroundColor = [self colorForIndex:indexPath.row];
}
#pragma mark TODO delete from server database
//method to delete an article form view and to call method to delete from database, as well as form server database
-(void)deleteArticle:(Article*)articleToDelete {
. . .
}
#pragma mark TODO delete from server database
//method to delete an article form view and to call method to delete from database, as well as form server database
-(void)archiveArticle:(Article*)articleToArchive {
. . .
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
A tableView calls the datasource methods to populate itself just after viewDidLoad. If, at that point, the data source (which is usually an Array) is empty, nothing will appear in the tableView. That is why one has to call reloadData after the data source is brought into existence. This is especially needed in cases the data source is being fetched asyncronously.
According to Apple's documentation about reloadData:
Call this method to reload all the data that is used to construct the
table, including cells, section headers and footers, index arrays, and
so on. For efficiency, the table view redisplays only those rows that
are visible. It adjusts offsets if the table shrinks as a result of
the reload. The table view's delegate or data source calls this
method when it wants the table view to completely reload its data. It
should not be called in the methods that insert or delete rows,
especially within an animation block implemented with calls to
beginUpdates and endUpdates
Took advice of #akashg and added [self.tableView reloadData]; after fetching data. And it worked. Though, I don't know the mechanism of why this happened. At least it works ) Anyone welcome to add their explanation. Thank you.
I have an App that compiles a list of customer specific reports for a customer after login. These reports are showing up and I have put a "view" button which is supposed to download the PDF file, and bring it up to view within the app.
At this stage, it looks as though I have a memory issue when the view button is pressed, and I am not sure how to find where the issue is. HEre is my code:
#import "reportsTestViewController.h"
#import "ReportsDataObject.h"
#import "Session.h"
#import "AFNetworking.h"
#interface reportsTestViewController ()
#end
#implementation reportsTestViewController
#synthesize response;
#synthesize myDataIvar;
#synthesize viewReportPressed;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
reportsTable.delegate = self;
reportsTable.dataSource = self;
self.sections = [[NSMutableDictionary alloc] init];
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
#pragma mark NSURLConnection Delegate Methods
//
//Create your request pointing to the test page
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.tesg.com.au/allCustBuild.php"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15.0];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
//initialize it when you create your connection
if (connection){
self.myDataIvar = [[NSMutableData alloc] init];
}
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
[self.myDataIvar setLength:0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.myDataIvar appendData:data];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"Connection Failed: %#", error.userInfo);
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
//this is where you would parse the data received back from the server
NSString *responseString = [[NSString alloc] initWithData:self.myDataIvar encoding:NSUTF8StringEncoding];
NSLog(#"Received Data: %#",responseString);
[self setupReportsFromJSONArray:self.myDataIvar];
}
-(void)connectionWasASuccess:(NSData *)data{
[self setupReportsFromJSONArray:data];
}
-(void)setupReportsFromJSONArray:(NSData*)dataFromReportsArray{
BOOL found;
NSError *error;
// NSMutableArray *reportsArray = [[NSMutableArray alloc] init];
NSArray *arrayFromServer = [NSJSONSerialization JSONObjectWithData:dataFromReportsArray options:0 error:&error];
if(error){
NSLog(#"error parsing the json data from server with error description - %#", [error localizedDescription]);
}
else {
reportsArray = [[NSMutableArray alloc] init];
for(NSDictionary *eachReport in arrayFromServer)
{
ReportsDataObject *report = [[ReportsDataObject alloc] initWithJSONData:eachReport];
[reportsArray addObject:report];
NSString *c = [[eachReport objectForKey:#"title"] substringToIndex:3];
found = NO;
for (NSString *str in [self.sections allKeys])
{
if ([str isEqualToString:c])
{
found = YES;
}
}
if (!found)
{
[self.sections setValue:[[NSMutableArray alloc] init] forKey:c];
}
}
}
NSLog(#"Array Populated");
NSLog(#"%u reports found",reportsArray.count);
//Now you have your reportsArray filled up with all your data objects
for (NSDictionary *eachReport in arrayFromServer)
{
[[self.sections objectForKey:[[eachReport objectForKey:#"title"] substringToIndex:3]] addObject:eachReport];
}
// Sort each section array
for (NSString *key in [self.sections allKeys])
{
[[self.sections objectForKey:key] sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES]]];
}
[reportsTable reloadData];
}
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)viewReportPressed:(UIButton*)button {
NSLog(#"Button successfully Pressed");
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:#"http://tesg.com.au/portal/media/reports/1367365180_367 Collins Passive April 2013.pdf"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *theResponse) {
NSURL *documentsDirectoryPath = [NSURL fileURLWithPath:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]];
return [documentsDirectoryPath URLByAppendingPathComponent:[theResponse suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(#"File downloaded to: %#", filePath);
}];
[downloadTask resume];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSUInteger count = [[self.sections allKeys] count];
NSLog(#"Number of sections: %d", count);
return count;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [[[self.sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] objectAtIndex:section];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
//We check against table to make sure we are displaying the right number of cells
// for the appropriate table. This is so that things will work even if one day you
//decide that you want to have two tables instead of one.
{
NSUInteger count = [[self.sections valueForKey:[[[self.sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] objectAtIndex:section]] count];
NSLog(#"Number of rows in section: %d", count);
return count;
}
}
//- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
//return [[self.sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
// }
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell =[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
//The beauty of this is that you have all your data in one object and grab WHATEVER you like
//This way in the future you can add another field without doing much.
NSUInteger count = [[self.sections allKeys] count];
if(count == 0){
cell.textLabel.text = #"no reports to show";
}
else{
NSDictionary *Reports = [[self.sections valueForKey:[[[self.sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section]] objectAtIndex:indexPath.row];
cell.textLabel.text = [Reports objectForKey:#"title"];
cell.detailTextLabel.text = [Reports objectForKey:#"building"];
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:self action:#selector(viewReportPressed:) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:#"View" forState:UIControlStateNormal];
button.frame = CGRectMake(180.0f, 5.0f, 150.0f, 30.0f);
[cell addSubview:button];
// in the future you can grab whatever data you need like this
//[currentReport buildingName], or [currentReport reportName];
}
return(cell);
}
#end
When the view button is pressed in any of the cells, I get the NSLog saying 'button successfully pressed' but it crashes immediately after with error
[downloadTask resume]; Thread 1:EXC_BAD_ACCESS (code=2, address=0x0)
Any ideas how to fix this?
If you get a EXC_BAD_ACCESS with an address of 0x0, that generally means that a nil value was used in a context where nil is not permitted.
In this case, it is a result of the fact that your URL string is not valid. Spaces are not allowed. Thus your resulting URL variable is nil, which will result in the behavior you describe. You probably want to percent escape the URL string, e.g.:
NSString *URLString = #"http://tesg.com.au/portal/media/reports/1367365180_367 Collins Passive April 2013.pdf";
NSURL *URL = [NSURL URLWithString:[URLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
To identify this sort of exception in the future, you can use exception breakpoints, which will often help identify the offending line of code. Or just carefully review your code code for situations where you may have unintentionally passed a nil value to a method that does not accept it.
If, however, you ever get a EXC_BAD_ACCESS with a value other than 0x0, that is often a result of an attempt to use a previously deallocated object. (Not always, but frequently. Fortunately, ARC has made this sort of problem less common.) Clearly, this is not the case here, but you asked how one would identify a situation where you attempt to use a previously deallocated object.
To diagnose that situation, you would generally turn on "zombies" (see Running Your Application with Diagnostics). By the way, once you finish your diagnostics with zombies, make sure you turn them back off, as you don't want to keep zombies turned on (especially in a production app).
Anyway, in this case, because the address was 0x0, it wasn't a result of a deallocated object, but rather the problem was the incorrect usage of a nil value, caused by the invalid URL.
In my UITableViewController there are custom cells populated from JSON. Each cell has a UILabel showing a text value (#"valoracion"). When the user selects a row, a detail view from the cell object is shown. At this detail view, the user can change the value of valoracion. If the user goes back to the UITableViewController, valoracion should show the new value, but actually it shows the old value.
What should I do to update valoracion in the UITableViewController, when the user goes back from the detail view to the UITableViewController.
UPDATED***
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cell =[tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (cell ==nil){
cell = [[CustomCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"Cell"];
}
//cell.backgroundColor = [UIColor clearColor];
cell.nombreEmpresaLabel.text = [[categorias objectAtIndex:indexPath.row] objectForKey:#"nombreEmpresa"];
//[[cell contentView] setBackgroundColor:[UIColor clearColor]];
//cell.textLabel.backgroundColor = nil;
// cell.detailTextLabel.backgroundColor=nil;
cell.textLabel.textColor = [UIColor whiteColor];
cell.direccionEmpresaLabel.text= [[categorias objectAtIndex:indexPath.row] objectForKey:#"direccionEmpresa"];
NSMutableString *logo = [[NSMutableString alloc]initWithString:#"http://mujercanariasigloxxi.appgestion.eu/logos/"];
NSString *imageURL = [[categorias objectAtIndex:indexPath.row] objectForKey:#"strImagen"];
cell.meGustaHits.text = [[categorias objectAtIndex:indexPath.row] objectForKey:#"valoracionEmpresa"];
if(imageURL != nil && ![imageURL isEqual:[NSNull null]])
{
[logo appendString:imageURL];
NSURL *logoURL = [NSURL URLWithString:logo];
NSData *logoData = [NSData dataWithContentsOfURL:logoURL];
cell.logoImage.image = [UIImage imageWithData:logoData];
}
else{
cell.logoImage.image = [UIImage imageNamed:#"icono80"];
}
return cell;
}
Inside viewDidLoad method:
//URL definition where php file is hosted
int categoriaID = [[categoriaDescription objectForKey:#"idCategoria"] intValue];
NSString *string = [NSString stringWithFormat:#"%d", categoriaID];
NSLog(#"CATEGORIA ID STGRING %#",string);
NSMutableString *ms = [[NSMutableString alloc] initWithString:#"http://mujercanariasigloxxi.appgestion.eu/app_php_files/empresaslist.php?id="];
[ms appendString:string];
// URL request
NSLog(#"URL = %#",ms);
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:ms]];
//URL connection to the internet
[[NSURLConnection alloc]initWithRequest:request delegate:self];
And now the JSON delegate methods and didSelecRowAtIndexpath method:
//methods to perform the connection and population of data
-(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
{
//if data received network indicator not visible
[UIApplication sharedApplication].networkActivityIndicatorVisible=NO;
//array waterfalls populated via JSON from database
categorias = [NSJSONSerialization JSONObjectWithData:data options:nil error:nil];
NSLog(#"THE DA TA &#",categorias);
[self.tableView reloadData];
}
//only in case of error
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
UIAlertView *errorView = [[UIAlertView alloc]initWithTitle:#"Error" message:#"The download could not complete - please make sure you are connected to eithre 3G or WiFi" delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[errorView show];
//if no connection network indicator not visible
[UIApplication sharedApplication].networkActivityIndicatorVisible=NO;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DetalleEmpresaViewController *detailViewController =[self.storyboard instantiateViewControllerWithIdentifier:#"detailEmpresaViewController"];
detailViewController.title =[[categorias objectAtIndex:indexPath.row]objectForKey:#"idCategoria"];
detailViewController.detalleDescription = [categorias objectAtIndex:indexPath.row];
[self.navigationController pushViewController:detailViewController animated:YES];
}
Try call [tableview reloadData] method and make sure that you have correct implementation of tableview delegate methods.
If place where you change and save your JSON is another, than tableViewController, then put reloadData method in viewDidAppear of tableViewController class. If you saving JSON in other method of same tableViewController, then place reloadData in last line of this method.
I have a table view with an image view behind it. I'm trying to figure out how to blur the image behind the table view similar to how it looks when you open control center.
Here is my view:
And here is the code for it:
#import "ToursAndConferencesViewController.h"
#import "RSSChannel.h"
#import "RSSItem.h"
#import "WebViewController.h"
#import "DTCustomColoredAccessory.h"
#import "SVProgressHUD.h"
#implementation ToursAndConferencesViewController
{
UIActivityIndicatorView *loadingIndicator;
}
#synthesize webViewController;
- (void)viewDidLoad
{
UIImageView *background = [[UIImageView alloc]init];
background.image = [UIImage imageNamed:#"plain_app-background.png"];
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
[self.tableView setBackgroundView:background];
self.title = #"Tours & Conferences";
[[SVProgressHUD appearance]setHudBackgroundColor:[UIColor blackColor]];
[[SVProgressHUD appearance]setHudForegroundColor:[UIColor whiteColor]];
[SVProgressHUD showWithStatus:#"Loading"];
// [SVProgressHUD showWithStatus:#"Loading" maskType:SVProgressHUDMaskTypeGradient];
}
- (void)viewDidDisappear:(BOOL)animated
{
[SVProgressHUD dismiss];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
NSLog(#"%# found a %# element", self, elementName);
if ([elementName isEqual:#"channel"])
{
// If the parser saw a channel, create new instance, store in our ivar
channel = [[RSSChannel alloc]init];
// Give the channel object a pointer back to ourselves for later
[channel setParentParserDelegate:self];
// Set the parser's delegate to the channel object
[parser setDelegate:channel];
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// return 0;
NSLog(#"channel items %d", [[channel items]count]);
return [[channel items]count];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 50;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// return nil;
UIImageView *image = [[UIImageView alloc]init];
image.image = [UIImage imageNamed:#"CellImage.png"];
UIImageView *background = [[UIImageView alloc]init];
background.image = [UIImage imageNamed:#"plain_app-background.png"];
UIImageView *highlightedCellImage = [[UIImageView alloc]init];
highlightedCellImage.image = [UIImage imageNamed:#"HighlightedCellImage"];
UIColor *kfbBlue = [UIColor colorWithRed:8.0/255.0f green:77.0/255.0f blue:139.0/255.0f alpha:1];
UIColor *kfbLightBlue = [UIColor colorWithRed:24.0/255.0f green:89.0/255.0f blue:147.0/255.0f alpha:1];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell"];
if (cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"UITableViewCell"];
cell.textLabel.font=[UIFont systemFontOfSize:16.0];
}
RSSItem *item = [[channel items]objectAtIndex:[indexPath row]];
[[cell textLabel]setText:[item title]];
NSLog(#"Date: %#", [item date]);
tableView.backgroundColor = [UIColor clearColor];
[self.tableView setSeparatorColor:kfbBlue];
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.textLabel.highlightedTextColor = kfbBlue;
cell.textLabel.font = [UIFont fontWithName:#"FranklinGothicStd-ExtraCond" size:20.0];
cell.textLabel.textColor = kfbLightBlue;
// cell.backgroundView = image;
cell.backgroundColor = [UIColor clearColor];
// cell.selectedBackgroundView = highlightedCellImage;
tableView.backgroundView = background;
DTCustomColoredAccessory *accessory = [DTCustomColoredAccessory accessoryWithColor:cell.textLabel.textColor];
accessory.highlightedColor = kfbBlue;
cell.accessoryView =accessory;
return cell;
}
- (void)fetchEntries
{
// Create a new data container for the stuff that comes back from the service
xmlData = [[NSMutableData alloc]init];
// Construct a URL that will ask the service for what you want -
// note we can concatenate literal strings together on multiple lines in this way - this results in a single NSString instance
NSURL *url = [NSURL URLWithString:#"http://kyfbnewsroom.com/category/conferences/feed"];
// Put that URL into an NSURLRequest
NSURLRequest *req = [NSURLRequest requestWithURL:url];
// Create a connection that will exchange this request for data from the URL
connection = [[NSURLConnection alloc]initWithRequest:req delegate:self startImmediately:YES];
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self)
{
[self fetchEntries];
}
return self;
}
// This method will be called several times as the data arrives
- (void)connection:(NSURLConnection *)conn didReceiveData:(NSData *)data
{
// Add the incoming chunk of data to the container we are keeping
// The data always comes in the correct order
[xmlData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)conn
{
/* We are just checking to make sure we are getting the XML
NSString *xmlCheck = [[NSString alloc]initWithData:xmlData encoding:NSUTF8StringEncoding];
NSLog(#"xmlCheck = %#", xmlCheck);*/
// [loadingIndicator stopAnimating];
[SVProgressHUD dismiss];
// Create the parser object with the data received from the web service
NSXMLParser *parser = [[NSXMLParser alloc]initWithData:xmlData];
// Give it a delegate - ignore the warning here for now
[parser setDelegate:self];
//Tell it to start parsing - the document will be parsed and the delegate of NSXMLParser will get all of its delegate messages sent to it before this line finishes execution - it is blocking
[parser parse];
// Get rid of the XML data as we no longer need it
xmlData = nil;
// Reload the table.. for now, the table will be empty
[[self tableView]reloadData];
NSLog(#"%#\n %#\n %#\n", channel, [channel title], [channel infoString]);
}
- (void)connection:(NSURLConnection *)conn didFailWithError:(NSError *)error
{
// Release the connection object, we're done with it
connection = nil;
// Release the xmlData object, we're done with it
xmlData = nil;
[SVProgressHUD dismiss];
// Grab the description of the error object passed to us
NSString *errorString = [NSString stringWithFormat:#"Fetch failed: %#", [error localizedDescription]];
// Create and show an alert view with this error displayed
UIAlertView *av = [[UIAlertView alloc]initWithTitle:#"Error" message:errorString delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[av show];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[[webViewController webView]loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"about:blank"]]];
// Push the web view controller onto the navigation stack - this implicitly creates the web view controller's view the first time through
// [[self navigationController]pushViewController:webViewController animated:YES];
[self.navigationController pushViewController:webViewController animated:YES];
// Grab the selected item
RSSItem *entry = [[channel items]objectAtIndex:[indexPath row]];
// Construct a URL with the link string of the item
NSURL *url = [NSURL URLWithString:[entry link]];
// Construct a request object with that URL
NSURLRequest *req = [NSURLRequest requestWithURL:url];
// Load the request into the web view
[[webViewController webView]loadRequest:req];
webViewController.hackyURL = url;
// Set the title of the web view controller's navigation item
// [[webViewController navigationItem]setTitle:[entry title]];
}
#end
Set your tableView to [UIColor clearColor];
Then download apple's UIImage+ImageEffects category files that contain helper methods to blur images.
You can follow this tutorial for code samples on image blurring
Here is my final result which is an image behind my tableView:
Now I know what an NSRangeException is. What I dont get is why on the first run through of my code it's fine, but when the same code (and variables) are setup the second time it fails. Even stepping through the code I can't see where it is differing from the first run through.
The whole code gets a set of JSON values from my php api (basically just client information, units booked in, date, etc.) It used to just display it in a UITableView, I decided to group my table by the date it was booked in (Apple should have a nicer way to do this in my opinion, perhaps extending their UITableView?)
my .h
#interface LTBViewController : UIViewController {
IBOutlet UITableView *mainTableView;
NSArray *tickets; //for storing our serialized JSON
NSMutableData *data; //for appending our JSON as we're getting it
NSString *token; //for security authentication with our custom API
NSMutableArray *days; //for storing any days with bookings retrieved from tickets
NSMutableDictionary *groupedTickets; //for storing tickets to correlate to days
NSArray *filteredArray; //for our finalized output
}
#end
most of my .m (the important parts)
- (void)viewDidAppear:(BOOL)animated
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://myurl.com/api/api.php?api=%#&function=%#", token, #"active"]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection connectionWithRequest:request delegate:self];
//[[NSURLConnection alloc] initWithRequest:request delegate:self]; old way of doing it
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//initialize from our settings file and make sure we're authenticated
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
token = [defaults objectForKey:#"api_token"];
NSLog(#"%#",token);
//initialize our sorting arrays
days = [[NSMutableArray alloc] init];
groupedTickets = [[NSMutableDictionary alloc] init];
filteredArray = [[NSArray alloc] init];
}
- (void)groupTable {
NSUInteger count = 0;
for (LTBViewController *ticket in tickets)
{
NSString *date = [[tickets objectAtIndex:count] objectForKey:#"dateIn"];
if (![days containsObject:date])
{
[days addObject:date];
[groupedTickets setObject:[NSMutableArray arrayWithObject:ticket] forKey:date];
}
else
{
[((NSMutableArray*)[groupedTickets objectForKey:date]) addObject:ticket];
}
count +=1;
}
count = 0;
[mainTableView reloadData];
}
//for when we get a response
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
data = [[NSMutableData alloc] init];
}
//for getting data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)thedata
{
[data appendData:(thedata)];
}
//for when connection finished
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
tickets = [NSJSONSerialization JSONObjectWithData:data options:NO error:nil];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[self groupTable]; //starts our grouping methods
}
//for when connection fails
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
UIAlertView *errorView = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Could not connect to server" delegate:nil cancelButtonTitle:#"dismiss" otherButtonTitles:nil, nil];
[errorView show];
}
//overriding the "delete" text for our UITableView
- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath {
return #"Close Ticket";
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSString *date = [days objectAtIndex:section];
return date;
}
- (int)numberOfSectionsInTableView:(UITableView *)tableView
{
return [days count];
}
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSString *date = [days objectAtIndex:section];
NSLog(#"%#", date);
return [[groupedTickets objectForKey:date] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MainCell"];
tableView.layer.cornerRadius = 5.0; //rounds our table corners
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:(UITableViewCellStyleSubtitle) reuseIdentifier:#"MainCell"];
}
//I'm sure theres a better way to do this but it works for now
NSString *date = [days objectAtIndex:[indexPath section]];
NSPredicate *search = [NSPredicate predicateWithFormat:#"dateIn == %#", date];
filteredArray = [tickets filteredArrayUsingPredicate:search];
//this right here is what fails the second time through at index 1
cell.textLabel.text = [NSString stringWithFormat:#"%#",[[filteredArray objectAtIndex:[indexPath row]] objectForKey:#"ticketnumber"] ];
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#", [[filteredArray objectAtIndex:[indexPath row]] objectForKey:#"customerName"]];
return cell;
}
When an item is clicked it moves to another view controller (that works alright) but when the back button is pressed from it is where this code fails. I would be appreciative of any extra sets of eyes going over it and helping me find my error. (or a better way of doing this)
Thanks!
Got it. I thought when I reassigned values to my NSMutableArray days and NSMutableDictionary groupedTickets it would just overwrite the previous values.
I performed release on view will disappear and it works now.
Still if there's a better way to do this I'd be interested in learning it.