UITableView not displaying parsed data - iphone

I have a UITableView which is setup in Interface Builder and connected properly to its class in Xcode. I also have a "Importer" Class which downloads and parses an RSS feed and stores the information in an NSMutableArray.
However I have verified the parsing is working properly (using breakpoints and NSlog) but no data is showing in the UITable View. Any ideas as to what the problem could be? I'm almost out of them.
It's based on the XML performance Apple example.
Update 2:
OK so after working through the below information, here's what I know:
The didParseIncidents:(NSArray *)parsedIncidents is not being called, but read on as to why.
When I launch the app, and reload the data, (void)parsedIncident:(Incident *)incident runs, but misses a break point in if (self.delegate != nil && [self.delegate respondsToSelector:#selector(parser:didParseIncidents:)]) {
[self.delegate parser:self didParseIncidents:parsedIncidents];
I suspect my table issues come from the fact that the above code isn't working properly - in the sample the breakpoint in if (self.delegate != nil && [self.delegate respondsToSelector:#selector(parser:didParseIncidents:)]) {
[self.delegate parser:self didParseIncidents:parsedIncidents]; runs
Doing a po parsedIncidents here results in a "Cannot access Memory 0x0" error, whereas in sample app gives an NS Array
In fact, it seems as though parsedIncidents count is 0 - but can't work out why.
Update:
Alright so here are the results:
"po incidents" on the parserDidEndParsingData breakpoint gave me a reply of "cannot access memory at address 0x0".
The po [self tableView] displays ">
Current language: auto; currently objective-c"
In the breakpoint under number of rows in sections, I got the following "()" message.
The IB items I checked and all were as you said. I suspect the array isn't being loaded from the above errors?
I've also added the parsing code, if that helps.
Here's the code for TableView.h:
#import <UIKit/UIKit.h>
#import "IncidentsImporter.h"
#class SongDetailsController;
#interface CurrentIncidentsTableViewController : UITableViewController <IncidentsImporterDelegate>{
NSMutableArray *incidents;
SongDetailsController *detailController;
UITableView *ctableView;
IncidentsImporter *parser;
}
#property (nonatomic, retain) NSMutableArray *incidents;
#property (nonatomic, retain, readonly) SongDetailsController *detailController;
#property (nonatomic, retain) IncidentsImporter *parser;
#property (nonatomic, retain) IBOutlet UITableView *ctableView;
// Called by the ParserChoiceViewController based on the selected parser type.
- (void)beginParsing;
#end
And the code for .m:
#import "CurrentIncidentsTableViewController.h"
#import "SongDetailsController.h"
#import "Incident.h"
#implementation CurrentIncidentsTableViewController
#synthesize ctableView, incidents, parser, detailController;
#pragma mark -
#pragma mark View lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
self.parser = [[IncidentsImporter alloc] init];
parser.delegate = self;
[parser start];
UIBarButtonItem *refreshButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:#selector(beginParsing)];
self.navigationItem.rightBarButtonItem = refreshButton;
[refreshButton release];
// Uncomment the following line to preserve selection between presentations.
//self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (void)viewWillAppear:(BOOL)animated {
NSIndexPath *selectedRowIndexPath = [ctableView indexPathForSelectedRow];
if (selectedRowIndexPath != nil) {
[ctableView deselectRowAtIndexPath:selectedRowIndexPath animated:NO];
}
}
// This method will be called repeatedly - once each time the user choses to parse.
- (void)beginParsing {
NSLog(#"Parsing has begun");
//self.navigationItem.rightBarButtonItem.enabled = NO;
// Allocate the array for song storage, or empty the results of previous parses
if (incidents == nil) {
NSLog(#"Grabbing array");
self.incidents = [NSMutableArray array];
} else {
[incidents removeAllObjects];
[ctableView reloadData];
}
// Create the parser, set its delegate, and start it.
self.parser = [[IncidentsImporter alloc] init];
parser.delegate = self;
[parser start];
}
/*
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
}
*/
/*
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}
*/
/*
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
}
*/
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Override to allow orientations other than the default portrait orientation.
return YES;
}
#pragma mark -
#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.
return [incidents count];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Table Cell Sought");
static NSString *kCellIdentifier = #"MyCell";
UITableViewCell *cell = [ctableView dequeueReusableCellWithIdentifier:kCellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellIdentifier] autorelease];
cell.textLabel.font = [UIFont boldSystemFontOfSize:14.0];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
cell.textLabel.text = #"Test";//[[incidents objectAtIndex:indexPath.row] title];
return cell;
}
/*
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// Return NO if you do not want the specified item to be editable.
return YES;
}
*/
/*
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
}
*/
/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
// Return NO if you do not want the item to be re-orderable.
return YES;
}
*/
#pragma mark -
#pragma mark Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
self.detailController.incident = [incidents objectAtIndex:indexPath.row];
[self.navigationController pushViewController:self.detailController animated:YES];
}
#pragma mark -
#pragma mark Memory management
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Relinquish ownership any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
// For example: self.myOutlet = nil;
}
- (void)parserDidEndParsingData:(IncidentsImporter *)parser {
[ctableView reloadData];
self.navigationItem.rightBarButtonItem.enabled = YES;
self.parser = nil;
}
- (void)parser:(IncidentsImporter *)parser didParseIncidents:(NSArray *)parsedIncidents {
//[incidents addObjectsFromArray: parsedIncidents];
// Three scroll view properties are checked to keep the user interface smooth during parse. When new objects are delivered by the parser, the table view is reloaded to display them. If the table is reloaded while the user is scrolling, this can result in eratic behavior. dragging, tracking, and decelerating can be checked for this purpose. When the parser finishes, reloadData will be called in parserDidEndParsingData:, guaranteeing that all data will ultimately be displayed even if reloadData is not called in this method because of user interaction.
if (!ctableView.dragging && !ctableView.tracking && !ctableView.decelerating) {
self.title = [NSString stringWithFormat:NSLocalizedString(#"Top %d Songs", #"Top Songs format"), [parsedIncidents count]];
[ctableView reloadData];
}
}
- (void)parser:(IncidentsImporter *)parser didFailWithError:(NSError *)error {
// handle errors as appropriate to your application...
}
- (void)dealloc {
[super dealloc];
}
#end
Here's the code for parser.h:
#import <UIKit/UIKit.h>
#import <libxml/tree.h>
#class IncidentsImporter, Incident;
// Protocol for the parser to communicate with its delegate.
#protocol IncidentsImporterDelegate <NSObject>
#optional
// Called by the parser when parsing is finished.
- (void)parserDidEndParsingData:(IncidentsImporter *)parser;
// Called by the parser in the case of an error.
- (void)parser:(IncidentsImporter *)parser didFailWithError:(NSError *)error;
// Called by the parser when one or more songs have been parsed. This method may be called multiple times.
- (void)parser:(IncidentsImporter *)parser didParseIncidents:(NSArray *)parsedIncidents;
#end
// This approach to parsing uses NSURLConnection to asychronously retrieve the XML data. libxml's SAX parsing supports chunked parsing, with no requirement for the chunks to be discrete blocks of well formed XML. The primary purpose of this class is to start the download, configure the parser with a set of C callback functions, and pass downloaded data to it. In addition, the class maintains a number of state variables for the parsing.
#interface IncidentsImporter : NSObject {
#private
id <IncidentsImporterDelegate> delegate;
// Reference to the libxml parser context
xmlParserCtxtPtr context;
NSURLConnection *rssConnection;
NSMutableArray *parsedIncidents;
// Overall state of the parser, used to exit the run loop.
BOOL done;
// State variable used to determine whether or not to ignore a given XML element
BOOL parsingAIncident;
// The following state variables deal with getting character data from XML elements. This is a potentially expensive
// operation. The character data in a given element may be delivered over the course of multiple callbacks, so that
// data must be appended to a buffer. The optimal way of doing this is to use a C string buffer that grows exponentially.
// When all the characters have been delivered, an NSString is constructed and the buffer is reset.
BOOL storingCharacters;
NSMutableData *characterBuffer;
// A reference to the current song the parser is working with.
Incident *currentIncident;
// The number of parsed songs is tracked so that the autorelease pool for the parsing thread can be periodically
// emptied to keep the memory footprint under control.
NSUInteger countOfParsedIncidents;
NSAutoreleasePool *downloadAndParsePool;
NSDateFormatter *parseFormatter;
}
#property (nonatomic, assign) id <IncidentsImporterDelegate> delegate;
#property (nonatomic, retain) NSMutableArray *parsedIncidents;
#property BOOL storingCharacters;
#property (nonatomic, retain) NSMutableData *characterBuffer;
#property BOOL done;
#property BOOL parsingAIncident;
#property NSUInteger countOfParsedIncidents;
#property (nonatomic, retain) Incident *currentIncident;
#property (nonatomic, retain) NSURLConnection *rssConnection;
#property (nonatomic, retain) NSDateFormatter *parseFormatter;
// The autorelease pool property is assign because autorelease pools cannot be retained.
#property (nonatomic, assign) NSAutoreleasePool *downloadAndParsePool;
- (void)downloadAndParse:(NSURL *)url;
- (void)finishedCurrentIncident;
- (void)start;
- (void)downloadStarted;
- (void)downloadEnded;
- (void)parseEnded;
- (void)parsedIncident:(Incident *)incident;
- (void)parseError:(NSError *)error;
- (void)addToParseDuration:(NSNumber *)duration;
#end
And parser.m:
#import "IncidentsImporter.h"
#import "Incident.h"
#import <libxml/tree.h>
static NSUInteger kCountForNotification = 10;
// Function prototypes for SAX callbacks. This sample implements a minimal subset of SAX callbacks.
// Depending on your application's needs, you might want to implement more callbacks.
static void startElementSAX(void *ctx, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI, int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes);
static void endElementSAX(void *ctx, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI);
static void charactersFoundSAX(void * ctx, const xmlChar * ch, int len);
static void errorEncounteredSAX(void * ctx, const char * msg, ...);
// Forward reference. The structure is defined in full at the end of the file.
static xmlSAXHandler simpleSAXHandlerStruct;
#implementation IncidentsImporter
#synthesize delegate, rssConnection, done, parsingAIncident, parsedIncidents, storingCharacters, currentIncident, countOfParsedIncidents, characterBuffer, parseFormatter, downloadAndParsePool;
- (void)start {
NSLog(#"URL Gained");
[[NSURLCache sharedURLCache] removeAllCachedResponses];
NSURL *url = [NSURL URLWithString:#"http://ax.phobos.apple.com.edgesuite.net/WebObjects/MZStore.woa/wpa/MRSS/newreleases/limit=300/rss.xml"];
[NSThread detachNewThreadSelector:#selector(downloadAndParse:) toTarget:self withObject:url];
}
- (void)downloadStarted {
NSLog(#"Download has begun");
NSAssert2([NSThread isMainThread], #"%s at line %d called on secondary thread", __FUNCTION__, __LINE__);
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
- (void)downloadEnded {
NSLog(#"Download has ended");
NSAssert2([NSThread isMainThread], #"%s at line %d called on secondary thread", __FUNCTION__, __LINE__);
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
- (void)parseEnded {
NSLog(#"Parsing has ended");
NSAssert2([NSThread isMainThread], #"%s at line %d called on secondary thread", __FUNCTION__, __LINE__);
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(parser:didParseIncidents:)] && [parsedIncidents count] > 0) {
[self.delegate parser:self didParseIncidents:parsedIncidents];
}
[self.parsedIncidents removeAllObjects];
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(parserDidEndParsingData:)]) {
[self.delegate parserDidEndParsingData:self];
}
}
- (void)parsedIncident:(Incident *)incident {
NSLog(#"Parsing has begun on thread");
NSAssert2([NSThread isMainThread], #"%s at line %d called on secondary thread", __FUNCTION__, __LINE__);
[self.parsedIncidents addObject:incident];
if (self.parsedIncidents.count > kCountForNotification) {
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(parser:didParseIncidents:)]) {
[self.delegate parser:self didParseIncidents:parsedIncidents];
}
[self.parsedIncidents removeAllObjects];
}
}
- (void)parseError:(NSError *)error {
NSLog(#"Parsing has an error");
NSAssert2([NSThread isMainThread], #"%s at line %d called on secondary thread", __FUNCTION__, __LINE__);
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(parser:didFailWithError:)]) {
[self.delegate parser:self didFailWithError:error];
}
}
- (void)addToParseDuration:(NSNumber *)duration {
NSAssert2([NSThread isMainThread], #"%s at line %d called on secondary thread", __FUNCTION__, __LINE__);
}
/*
This method is called on a secondary thread by the superclass. We have asynchronous work to do here with downloading and parsing data, so we will need a run loop to prevent the thread from exiting before we are finished.
*/
- (void)downloadAndParse:(NSURL *)url {
NSLog(#"Downloading and Parsing");
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
done = NO;
self.parseFormatter = [[[NSDateFormatter alloc] init] autorelease];
[parseFormatter setDateStyle:NSDateFormatterLongStyle];
[parseFormatter setTimeStyle:NSDateFormatterNoStyle];
// necessary because iTunes RSS feed is not localized, so if the device region has been set to other than US
// the date formatter must be set to US locale in order to parse the dates
[parseFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:#"AU"] autorelease]];
self.characterBuffer = [NSMutableData data];
[[NSURLCache sharedURLCache] removeAllCachedResponses];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:url];
// create the connection with the request and start loading the data
rssConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
// This creates a context for "push" parsing in which chunks of data that are not "well balanced" can be passed
// to the context for streaming parsing. The handler structure defined above will be used for all the parsing.
// The second argument, self, will be passed as user data to each of the SAX handlers. The last three arguments
// are left blank to avoid creating a tree in memory.
context = xmlCreatePushParserCtxt(&simpleSAXHandlerStruct, self, NULL, 0, NULL);
[self performSelectorOnMainThread:#selector(downloadStarted) withObject:nil waitUntilDone:NO];
if (rssConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
// Release resources used only in this thread.
xmlFreeParserCtxt(context);
self.characterBuffer = nil;
self.parseFormatter = nil;
self.rssConnection = nil;
self.currentIncident = nil;
[downloadAndParsePool release];
self.downloadAndParsePool = nil;
}
#pragma mark NSURLConnection Delegate methods
/*
Disable caching so that each time we run this app we are starting with a clean slate. You may not want to do this in your application.
*/
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
return nil;
}
// Forward errors to the delegate.
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
done = YES;
[self performSelectorOnMainThread:#selector(parseError:) withObject:error waitUntilDone:NO];
}
// Called when a chunk of data has been downloaded.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"Did receive data");
NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
// Process the downloaded chunk of data.
xmlParseChunk(context, (const char *)[data bytes], [data length], 0);
NSTimeInterval duration = [NSDate timeIntervalSinceReferenceDate] - start;
[self performSelectorOnMainThread:#selector(addToParseDuration:) withObject:[NSNumber numberWithDouble:duration] waitUntilDone:NO];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"Connection done loading");
[self performSelectorOnMainThread:#selector(downloadEnded) withObject:nil waitUntilDone:NO];
NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
// Signal the context that parsing is complete by passing "1" as the last parameter.
xmlParseChunk(context, NULL, 0, 1);
NSTimeInterval duration = [NSDate timeIntervalSinceReferenceDate] - start;
[self performSelectorOnMainThread:#selector(addToParseDuration:) withObject:[NSNumber numberWithDouble:duration] waitUntilDone:NO];
[self performSelectorOnMainThread:#selector(parseEnded) withObject:nil waitUntilDone:NO];
// Set the condition which ends the run loop.
done = YES;
}
#pragma mark Parsing support methods
static const NSUInteger kAutoreleasePoolPurgeFrequency = 20;
- (void)finishedCurrentIncident {
[self performSelectorOnMainThread:#selector(parsedIncident:) withObject:currentIncident waitUntilDone:NO];
// performSelectorOnMainThread: will retain the object until the selector has been performed
// setting the local reference to nil ensures that the local reference will be released
self.currentIncident = nil;
countOfParsedIncidents++;
// Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the
// size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but
// taking this action too frequently would be wasteful and reduce performance.
if (countOfParsedIncidents == kAutoreleasePoolPurgeFrequency) {
[downloadAndParsePool release];
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
countOfParsedIncidents = 0;
}
}
/*
Character data is appended to a buffer until the current element ends.
*/
- (void)appendCharacters:(const char *)charactersFound length:(NSInteger)length {
[characterBuffer appendBytes:charactersFound length:length];
}
- (NSString *)currentString {
// Create a string with the character data using UTF-8 encoding. UTF-8 is the default XML data encoding.
NSString *currentString = [[[NSString alloc] initWithData:characterBuffer encoding:NSUTF8StringEncoding] autorelease];
[characterBuffer setLength:0];
return currentString;
}
#end
#pragma mark SAX Parsing Callbacks
// The following constants are the XML element names and their string lengths for parsing comparison.
// The lengths include the null terminator, to ensure exact matches.
static const char *kName_Item = "item";
static const NSUInteger kLength_Item = 5;
static const char *kName_Title = "title";
static const NSUInteger kLength_Title = 6;
static const char *kName_Category = "link";
static const NSUInteger kLength_Category = 9;
static const char *kName_Itms = "itms";
static const NSUInteger kLength_Itms = 5;
static const char *kName_Artist = "description";
static const NSUInteger kLength_Artist = 7;
static const char *kName_Album = "guid";
static const NSUInteger kLength_Album = 6;
static const char *kName_ReleaseDate = "pubDate";
static const NSUInteger kLength_ReleaseDate = 12;
/*
This callback is invoked when the parser finds the beginning of a node in the XML. For this application,
out parsing needs are relatively modest - we need only match the node name. An "item" node is a record of
data about a song. In that case we create a new Song object. The other nodes of interest are several of the
child nodes of the Song currently being parsed. For those nodes we want to accumulate the character data
in a buffer. Some of the child nodes use a namespace prefix.
*/
static void startElementSAX(void *ctx, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI,
int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes) {
IncidentsImporter *parser = (IncidentsImporter *)ctx;
// The second parameter to strncmp is the name of the element, which we known from the XML schema of the feed.
// The third parameter to strncmp is the number of characters in the element name, plus 1 for the null terminator.
if (prefix == NULL && !strncmp((const char *)localname, kName_Item, kLength_Item)) {
NSLog(#"Found node");
Incident *newIncident = [[Incident alloc] init];
parser.currentIncident = newIncident;
[newIncident release];
parser.parsingAIncident = YES;
} else if (parser.parsingAIncident && ( (prefix == NULL && (!strncmp((const char *)localname, kName_Title, kLength_Title) || !strncmp((const char *)localname, kName_Category, kLength_Category))) || ((prefix != NULL && !strncmp((const char *)prefix, kName_Itms, kLength_Itms)) && (!strncmp((const char *)localname, kName_Artist, kLength_Artist) || !strncmp((const char *)localname, kName_Album, kLength_Album) || !strncmp((const char *)localname, kName_ReleaseDate, kLength_ReleaseDate))) )) {
parser.storingCharacters = YES;
}
}
/*
This callback is invoked when the parse reaches the end of a node. At that point we finish processing that node,
if it is of interest to us. For "item" nodes, that means we have completed parsing a Song object. We pass the song
to a method in the superclass which will eventually deliver it to the delegate. For the other nodes we
care about, this means we have all the character data. The next step is to create an NSString using the buffer
contents and store that with the current Song object.
*/
static void endElementSAX(void *ctx, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI) {
IncidentsImporter *parser = (IncidentsImporter *)ctx;
if (parser.parsingAIncident == NO) return;
if (prefix == NULL) {
if (!strncmp((const char *)localname, kName_Item, kLength_Item)) {
[parser finishedCurrentIncident];
parser.parsingAIncident = NO;
} else if (!strncmp((const char *)localname, kName_Title, kLength_Title)) {
NSLog(#"Parsing title");
parser.currentIncident.title = [parser currentString];
} else if (!strncmp((const char *)localname, kName_Category, kLength_Category)) {
NSLog(#"Parsing url");
parser.currentIncident.link = [NSURL URLWithString: [parser currentString]];
}
else if (!strncmp((const char *)localname, kName_Artist, kLength_Artist)) {
NSLog(#"Parsing description");
parser.currentIncident.description = [parser currentString];
} else if (!strncmp((const char *)localname, kName_Album, kLength_Album)) {
NSLog(#"Parsing guid");
parser.currentIncident.guid = [parser currentString];
} else if (!strncmp((const char *)localname, kName_ReleaseDate, kLength_ReleaseDate)) {
NSLog(#"Parsing date");
NSString *dateString = [parser currentString];
parser.currentIncident.pubDate = [parser.parseFormatter dateFromString:dateString];
}
}
parser.storingCharacters = NO;
}
/*
This callback is invo

With that line commented out, how are you getting the incidents?
- (void)parser:(IncidentsImporter *)parser didParseIncidents:(NSArray *)parsedIncidents {
//[incidents addObjectsFromArray: parsedIncidents];
Calling reloadData when incidents has not changed does not really do anything.
In that same function, you only call reloadData if the table is stationary. If you do not call it, you should set a flag to call reloadData once the table is stationary. There are a few UIScrollView delegate callbacks to detect that.

Some things to check:
If you set a breakpoint in parserDidEndParsingData:, is ctableView pointing at a valid UITableView object, not nil?
If you type "po incidents" at your gdb prompt on that line, are your incidents all loaded?
If you type "po [self tableView]", does it display the same object as "po ctableView"?
If you set a breakpoint in tableView:numberOfRowsInSection: and do "po incidents", are they all loaded there? (It will hit that breakpoint multiple times, so keep hitting continue and make sure the incidents eventually show up there.)
If you have IB in list view mode, your tableView should appear to be nested under your CurrentIncidentsTableViewController. This indicates that the tableView is connected to the UITableViewController's view property. (BTW, if you have this configured property, you can access the tableView from your controller using self.tableView, so you technically don't need ctableView.)
The tableView's dataSource and delegate properties should be connected to your CurrentIncidentsTableViewController in IB.

Try reloading the table after parsing is done .
[table reloadData];
table is the instance of the table view created by interface builder.
This link gives a sample program for parsing the xml and displaying its contents in the table.Have a look at this.Hope this helps you also.
All the best.

Related

UISegmentedcontrol for sorting and filtering core data

I have done the following task of taking data from a csv file and publishing it in a table view. What i would like to do is use uisegmentented control to sort and filter the core data which i have.
I have a list of names somewhat like this and i have it stored in the data model as firstname, lastname and gender. I have designed the following screenshot. By clicking on the a-z button or z-a button. The data should sort and filter according to gender like male,female or both.
I am new to core data(sorting and filtering) and UISegmented controls. Could you please help me in doing the above tasks in detail and how you have done it.
Following is the code for the work i have done till now. And i am using xib's.
RootViewController.h
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#interface RootViewController : UITableViewController
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#property (retain) NSArray *people;
//! Filtering view from XIB file
#property (nonatomic, retain) IBOutlet UIView *filterSortView;
//! Filter control
#property (nonatomic, retain) IBOutlet UISegmentedControl *filterControl;
//! Sort control
#property (nonatomic, retain) IBOutlet UISegmentedControl *sortControl;
#end
Then we RootViewController.m
#import "RootViewController.h"
#import "Person.h"
#implementation RootViewController
#synthesize people;
#synthesize managedObjectContext=__managedObjectContext;
// Sorting & filtering UI components
#synthesize filterSortView, filterControl, sortControl;
#pragma mark -
#pragma mark View Lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.title = #"People";
// Get our people array - this next block of code could probably be extracted out to a private
// method and generalized for different fetch request types
NSEntityDescription *personEntity = [NSEntityDescription entityForName:#"Person"
inManagedObjectContext:self.managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:personEntity];
NSError *error = nil;
self.people = [self.managedObjectContext executeFetchRequest:request error:&error];
if (error)
{
[NSException raise:NSInternalInconsistencyException format:#"Could not fetch Core Data records: %#",error];
}
[request release];
// Now do a secondary load from another XIB file (other than the main table view)
[[NSBundle mainBundle] loadNibNamed:#"FilterSortView" owner:self options:nil];
// This disables the default selection
self.filterControl.selectedSegmentIndex = -1;
self.sortControl.selectedSegmentIndex = -1;
// Now register for events when the value changes
// TODO: write method implementations for each of these and then uncomment these lines.
// [self.filterControl addTarget:self action:#selector( -- TODO -- ) forControlEvents:UIControlEventValueChanged];
// [self.sortControl addTarget:self action:#selector( -- TODO -- ) forControlEvents:UIControlEventValueChanged];
}
- (void)viewDidUnload
{
[super viewDidUnload];
self.filterControl = nil;
self.sortControl = nil;
self.filterSortView = nil;
}
#pragma mark -
#pragma mark UITableViewDataSource
// Customize the number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.people count];
}
#pragma mark -
#pragma mark UITableViewDelegate
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
Person *person = [self.people objectAtIndex:[indexPath row]];
cell.textLabel.text = [NSString stringWithFormat:#"%#, %#",person.lastName,person.firstName];
return cell;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
return self.filterSortView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return self.filterSortView.frame.size.height;
}
#pragma mark -
#pragma mark Class Plumbing
- (void)dealloc
{
[filterControl release];
[sortControl release];
[filterSortView release];
[people release];
[__managedObjectContext release];
[super dealloc];
}
#end
Then we Have FilteredListAppDelegate.h
#import <UIKit/UIKit.h>
#interface FilteredListAppDelegate : NSObject <UIApplicationDelegate> {
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
#end
Then we have FilteredListAppDelegate.m
#import "FilteredListAppDelegate.h"
#import "RootViewController.h"
#import "Person.h"
// Private methods
#interface FilteredListAppDelegate ()
- (BOOL) _populateCoreData;
#end
#implementation FilteredListAppDelegate
#synthesize window=_window;
#synthesize managedObjectContext=__managedObjectContext;
#synthesize managedObjectModel=__managedObjectModel;
#synthesize persistentStoreCoordinator=__persistentStoreCoordinator;
#synthesize navigationController=_navigationController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Prepopulate Core Data with our data, if necessary.
NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];
if ([settings objectForKey:#"core_data_populated"] == nil)
{
BOOL success = [self _populateCoreData];
if (success)
{
[settings setValue:[NSNumber numberWithBool:YES] forKey:#"core_data_populated"];
}
}
// Override point for customization after application launch.
// Add the navigation controller's view to the window and display.
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
/*
Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
*/
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
/*
Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
*/
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
/*
Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
*/
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
/*
Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
*/
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Saves changes in the application's managed object context before the application terminates.
[self saveContext];
}
- (void)dealloc
{
[_window release];
[__managedObjectContext release];
[__managedObjectModel release];
[__persistentStoreCoordinator release];
[_navigationController release];
[super dealloc];
}
- (void)awakeFromNib
{
RootViewController *rootViewController = (RootViewController *)[self.navigationController topViewController];
rootViewController.managedObjectContext = self.managedObjectContext;
}
- (void)saveContext
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
{
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
#pragma mark - Private Methods
/**
* Hacky method to just throw a bunch of data into Core Data.
*/
- (BOOL) _populateCoreData
{
NSManagedObjectContext *context = [self managedObjectContext];
// Get the names out of the text file
NSError *error = nil;
NSString *filename = [[NSBundle mainBundle] pathForResource:#"actors_by_gender" ofType:#"csv"];
NSString *fileContents = [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:&error];
if (error)
{
[NSException raise:NSInternalInconsistencyException format:#"Unable to read text file for adding to Core Data"];
}
NSArray *namesChoppedByNewline = [fileContents componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
for (NSString *singleNameRecord in namesChoppedByNewline)
{
NSArray *attributesChoppedByComma = [singleNameRecord componentsSeparatedByString:#","];
// Now make each record a new Core Data object
Person *newPerson = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:context];
newPerson.firstName = [attributesChoppedByComma objectAtIndex:0];
newPerson.lastName = [attributesChoppedByComma objectAtIndex:1];
newPerson.gender = [attributesChoppedByComma objectAtIndex:2];
}
if (![context save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
[NSException raise:NSInternalInconsistencyException
format:#"Couldn't save Core Data data for Person entity import. Reason: %#",error];
}
return YES;
}
#pragma mark - Core Data stack
/**
Returns the managed object context for the application.
If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
*/
- (NSManagedObjectContext *)managedObjectContext
{
if (__managedObjectContext != nil)
{
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
__managedObjectContext = [[NSManagedObjectContext alloc] init];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return __managedObjectContext;
}
/**
Returns the managed object model for the application.
If the model doesn't already exist, it is created from the application's model.
*/
- (NSManagedObjectModel *)managedObjectModel
{
if (__managedObjectModel != nil)
{
return __managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"FilteredList" withExtension:#"momd"];
__managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return __managedObjectModel;
}
/**
Returns the persistent store coordinator for the application.
If the coordinator doesn't already exist, it is created and the application's store added to it.
*/
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil)
{
return __persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"FilteredList.sqlite"];
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
{
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __persistentStoreCoordinator;
}
#pragma mark - Application's Documents directory
/**
Returns the URL to the application's Documents directory.
*/
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
#end
Thank you for the help.
You need to create and set an instance of NSSortDescriptor on your fetch request before you execute it.
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"firstName" ascending:YES];
By switching the ascending parameter based on your UISegmentedControl state, you can easily return the results in a-z or z-a order.
Alternatively, you could apply the same sort descriptor to your array of people using sortedArrayUsingDescriptors like so:
NSArray *array = [[self people] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
Keep in mind however that as you toggle the segmented control, you will either need to perform your fetch request again, or sort your array into a new array (perhaps use NSMutableArray instead).
To implement your segmented control, you will need to add a target and selector:
[[self segmentedControl] addTarget:self action:#selector(segmentedControlEventHandler:) forControlEvents:UIControlEventValueChanged];
Then use the following method to amend and update your sort descriptor and table view.
- (void)segmentedControlEventHandler:(id)sender
{
UISegmentedControl *segmentedControl = (UISegmentedControl *)sender;
if([segmentedControl selectedSegmentIndex] == 0)
{
//segment 1 selected
//set BOOL value for ascending sort descriptor
//update table view -> [[self tableView] reloadData];
}
else
{
//segment 2 selected
}
}
to filter by gender: NSPredicate *predicatem =[NSPredicate predicateWithFormat:#"gender == %#", #"m" ];
//give the predicate to the fecth request
request.predicate=predicatem;

Custom KalDataSource I tried to implement it but I'm getting an error

I try using a custom kal datasource for my calendar. I succeeded in getting the data, and when I tried to run it I keep getting this error:
-[NSNull cc_componentsForMonthDayAndYear]: unrecognized selector sent to instance 0x2823fb8
My code
// KalParseDataSource.m
#import "KalParseDataSource.h"
#import <Parse/Parse.h>
#implementation KalParseDataSource
static BOOL IsDateBetweenInclusive(NSDate *date, NSDate *begin, NSDate *end)
{
return [date compare:begin] != NSOrderedAscending && [date compare:end] != NSOrderedDescending;
}
- (id)init
{
if ((self = [super init])) {
items = [[NSMutableArray alloc] init];
events= [[NSMutableArray alloc] init];
}
return self;
}
#pragma mark UITableViewDataSource protocol conformance
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"MyCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.textLabel.text = #"Filler text";
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 0;
}
- (void)presentingDatesFrom:(NSDate *)fromDate to:(NSDate *)toDate delegate:(id<KalDataSourceCallbacks>)delegate{
NSLog(#"getting data");
if ([events count] > 0) {
[delegate loadedDataSource:self];
return;
}
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:#"yyyy-MM-dd hh:mm:ss"];
PFUser *user = [PFUser currentUser];
PFQuery *query = [PFQuery queryWithClassName:#"CalendarEvents"];
[query whereKey:#"user" equalTo:user];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
[events removeAllObjects];
[events addObjectsFromArray:objects];
[delegate loadedDataSource:self];
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
- (NSArray *)markedDatesFrom:(NSDate *)fromDate to:(NSDate *)toDate {
return [[self tagsFrom:fromDate to:toDate] valueForKeyPath:#"date"];
}
- (void)loadItemsFromDate:(NSDate *)fromDate toDate:(NSDate *)toDate {
[items addObjectsFromArray:[self tagsFrom:fromDate to:toDate]];
}
- (NSArray *)tagsFrom:(NSDate *)fromDate to:(NSDate *)toDate
{
NSMutableArray *matches = [NSMutableArray array];
for (PFObject *event in events){
if (IsDateBetweenInclusive([event objectForKey:#"event_date"], fromDate, toDate)){
[matches addObject:event];
}
}
return matches;
}
- (void)removeAllItems{
[items removeAllObjects];
}
#end
My view controller holding the calendar.
#import "MainMenuViewController.h"
#import "Kal.h"
#import "KalParseDataSource.h"
#interface MainMenuViewController ()
#end
#implementation MainMenuViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
id<KalDataSource> source = [[KalParseDataSource alloc] init];
KalViewController *calendar = [[KalViewController alloc] init];
calendar.dataSource = source;
[self addChildViewController:calendar];
[calendar didMoveToParentViewController:self];
[self.view addSubview:calendar.view];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
I'm stuck with this for 3 days, Can anyone give simple example in using this 3rd party framework https://github.com/klazuka/Kal ? The one on the repo seems a bit complicated, I just want to use a custom data source.
it seems that the function cc_componentsForMonthDayAndYear you are calling with null just put a debug there from wherever the function is being called and check if the function being called is on NSDate and it is not released.
And you can always Enable Zombies to find the released object when crash happens. You can track if any variable gets released via setting NSZombieEnabled to YES. With zombies enabled, messages to deallocated objects will no longer behave strangely or crash in difficult-to-understand ways, but will instead log a message and die in a predictable and debugger-breakpointable way.
You can set NSZombieEnabled by the following steps.
Select Product from the menu bar above. Keep alt/option pressed and select "Test..." or "Run...".
1.
Go to the Arguments tab, and add NSZombieEnabled YES in the "Environment Variables" section.
OR
2.
Go to the Diagnostics tab, and check Enable Zombie Objects in the "Memory Management" section.

NSZombieEnabled prevents my app from crashing

So I've been debugging like a mad men using NSZombiesEnabled and NSZombies in Instruments. However when running the app using zombies it seems to resolve my issue. When I run the app without NSZombiesEnabled or NSZombies in instruments it crashes. Any idea on how to deal with this?
So the issue is that I am releasing something twice, but can't seem to find where I am doing this. Turning on NSZombieEnabled won't help as the program runs fine without telling me where I am over releasing.
So I think I kind of know where it's crashing, I have this globalArray Singleton class that I am creating:
extern NSString * const kClearDataSource;
#interface AHImageDataSource : NSObject
+ (AHImageDataSource *)sharedDataSource;
- (void) clearDataSource;
- (void) addObject:(id) object;
- (void) addObject:(id)object atIndex:(int) index;
- (int) count;
- (id) objectAtIndex:(int) index;
#end
NSString * const kClearDataSource = #"clearDataSource";
#interface AHImageDataSource()
{
NSMutableArray * imageDataSource_;
}
#property (nonatomic, retain) NSMutableArray * imageDataSource_;
#end
#implementation AHImageDataSource
#synthesize imageDataSource_;
+ (AHImageDataSource *)sharedDataSource {
static AHImageDataSource *_sharedClient = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedClient = [[self alloc] init];
});
return _sharedClient;
}
- (id)init {
self = [super init];
if (!self) {
return nil;
}
NSMutableArray * temp = [[NSMutableArray alloc] initWithCapacity:200];
self.imageDataSource_ = temp;
[temp release];
return self;
}
-(void) clearDataSource
{
if ([self.imageDataSource_ count] > 0){
[self.imageDataSource_ removeAllObjects];
}
}
- (void) addObject:(id) object
{
[self.imageDataSource_ addObject:object];
}
- (void) addObject:(id)object atIndex:(int) index
{
[self.imageDataSource_ insertObject:object atIndex:index];
}
- (int) count
{
return [self.imageDataSource_ count];
}
- (id) objectAtIndex:(int) index
{
if (index >= 0 && index < [self.imageDataSource_ count]){
return [self.imageDataSource_ objectAtIndex:index];
}
return nil;
}
- (void) dealloc
{
[super dealloc];
[imageDataSource_ release];
}
#end
at one point of the code I am trying to remove all of the objects in the array and then adding some stuff in. When that happen the crashed happened.
This part of the code crashes the second time it was executed:
NSArray *arr = [response valueForKey:#"data"];
if ([arr count] > 0){
[[AHImageDataSource sharedDataSource] clearDataSource];
}
for (NSDictionary * data in arr){
AHInstagramImageData * imgData = [[AHInstagramImageData alloc] initWithData:data];
[[AHImageDataSource sharedDataSource] addObject:imgData];
[imgData release];
}
You should definitely not do [super dealloc] first in your -dealloc method. It must come last.
Go go Product -> Analyze. The messages displayed will give you the solution or an idea.
Your app crashes when an object that has been deallocated is sent a message. NSZombiesEnabled prevents your app from crashing because it holds on to all deallocated objects (and thus leaks everything). It will print a message in the console when a deallocated object is sent a message (which would normally crash your app). Something to the affect of "message 'bar' sent to deallocated object 'foo'" (or something like that). It does not actually pause execution of your app.
When you've passed the point where you know your app generally crashes, check the console log for a message similar to the one above.

Why cant I pass this variable from one class to another

I am stuck and need some help understanding why this is not working.
I want to be able to download the HTML of a page and then format it to show correctly, the code inside the second class (spriing) will download and display the HTML in a UITextView if it is placed inside the ViewController, however this is breaking the MVC right?
So could anyone tell me why I am getting the out of scope error on the mStringData variable?
My classes are below:
I have one class which is a view controller;
//Class for the download and processing of data from website
#import "FirstViewController.h"
#implementation FirstViewController
// The designated initializer. Override to perform setup that is required before the view is loaded.
//- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
// if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// // Custom initialization
//}
// return self;
//}
/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
}
*/
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
spriing = [Spriing new];
[spriing downloadData:#"http://www.spriing.co.uk/services/"];
SpriingTxt.text = spriing.mStringData;
}
/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
[mRecData release];
[mStringData release];
}
And a separate class;
#import "Spriing.h"
#implementation Spriing
#synthesize mStringData;
#synthesize mRecData;
- (void)downloadData: (NSString*) URL{
mBaseURL = URL;
// Create the request.
NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:mBaseURL]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
mCon=[[NSURLConnection alloc] initWithRequest:request delegate:self];
if (mCon)
{
// create var to store data
mRecData = [[NSMutableData data] retain];
}
else
{
// Inform the user that the connection failed.
}
}
//If the connection is reset
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
//reset the data length
[mRecData setLength:0];
}
//Obtaining new data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
//Add any newly recieved data to the currently stored data
[mRecData appendData:data];
}
//If something went wrong
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
//Release the connection
[mCon release];
//Release the data
[mRecData release];
//Alert the user
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:#"Error!"
message:#"No internet connection!" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil] autorelease];
[alert show];
[alert release];
}
//When its done
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//NSLog(#"finished");
// Once this method is invoked, "responseData" contains the complete result
self.mStringData = [[[NSString alloc] initWithData:mRecData encoding:NSUTF8StringEncoding] retain];
//NSLog(#"%#", mStringData);
self.mStringData = [self processData:mStringData];
//NSLog(#"%#", mStringData);
//SpriingTxt.text = mStringData;
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
//mStringData = nil;
}
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse
{
[mBaseURL autorelease];
mBaseURL = [[request URL] retain];
return request;
}
-(NSString*) processData: (NSString*) string
{
NSMutableString *html = [NSMutableString stringWithCapacity:[string length]];
NSScanner *scanner = [NSScanner scannerWithString:string];
NSString *tempText = nil;
while (![scanner isAtEnd])
{
[scanner scanUpToString:#"<" intoString:&tempText];
if (tempText != nil)
[html appendString:tempText];
[scanner scanUpToString:#">" intoString:NULL];
if (![scanner isAtEnd])
[scanner setScanLocation:[scanner scanLocation] + 1];
tempText = nil;
}
return html;
}
- (void) dealloc
{
[super dealloc];
//[mStringData release];
}
#end
You are starting an asynchronous request for a URL which will take some time. Although it returns immediately, it doesn't imply that the data has been download. NSURLRequest's delegate will be notified when the data has finished downloading. It is not until then that there is data in mStringData which is probably nil prior to being assigned the downloaded data. So when you do SpriingTxt.text = spriing.mStringData; immediately after an asynchronous request without the data being downloaded, SpriingTxt.text is assigned nil.
To resolve this, you can either make a synchronous request which will block until the data has been downloaded which is generally a bad idea or you can message via delegates or notifications to your view controller when the data of your asynchronous request has been downloaded.
To implement the delegate
Delegates are implemented using protocols. You will create a delegate property in the delegating object which would be Spriing as it will let the delegate know when the string has been downloaded and the view controller will be its delegate as it wants to know when the data is available so that it can update its view. Delegates are usually not retained as most times it is the object that creates them that becomes its delegate. So retaining the delegate would create a retain cycle in such instances. There are lots of tutorials about creating the delegates. A rough implementation would be,
in Spriing.h
#protocol SpriinDelegate;
#interface Spriing:... {
id<SpriingDelegate> delegate;
...
}
#property (nonatomic, assign) id<SpriingDelegate> delegate;
...
#end
#protocol SpriingDelegate
- (void)spriing:(Spriing*)aSpriing didFinishDownloadingString:(NSString*)aString;
#end
in Spriing.m
#implementation Spriing
#synthesize delegate;
...
//When its done
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
...
self.mStringData = [self processData:mStringData];
if ( self.delegate && [self.delegate respondsToSelector:#selector(spriing:didFinishDownloadingString:)]) {
[self.delegate spriing:self didFinishDownloadingString:self.mStringData];
}
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
...
#end
in the view controller,
- (void)viewDidLoad {
[super viewDidLoad];
spriing = [Spriing new];
spriing.delegate = self;
[spriing downloadData:#"http://www.spriing.co.uk/services/"];
}
- (void)spriing:(Spriing*)aSpriing didFinishDownloadingString:(NSString*)aString {
SpriingText.text = aString;
}
...

Using Cocoa Touch Tutorial: Extract Address Book Address Values on iPhone OS

I've followed the following tutorial, in the simulator it works great, however on my phone when select the address, Google maps launchs, I think I've fried my brain on this. I am using this in conjunction to a NavBarContolloer Any help would be great.
Taken from: Cocoa Touch Tutorial: Extract Address Book Address Values on iPhone OS
Here's the code:
#import "RootViewController.h"
#import "SecondViewController.h"
#import "ThirdViewController.h"
#import "FourthViewController.h"
#implementation ThirdViewController
#synthesize fourthViewController;
#synthesize firstName;
#synthesize lastName;
#synthesize addressLabel;
-(IBAction)switchPage:(id)sender
{
if(self.fourthViewController == nil)
{
FourthViewController *fourthView = [[FourthViewController alloc]
initWithNibName:#"FourthView" bundle:[NSBundle mainBundle]];
self.fourthViewController = fourthView;
[fourthView release];
}
[self.navigationController pushViewController:self.fourthViewController animated:YES];
}
-(IBAction)getContact {
// creating the picker
ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
// place the delegate of the picker to the controll
picker.peoplePickerDelegate = self;
// showing the picker
[self presentModalViewController:picker animated:YES];
// releasing
[picker release];
}
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker {
// assigning control back to the main controller
[self dismissModalViewControllerAnimated:YES];
}
- (BOOL)peoplePickerNavigationController: (ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person {
// setting the first name
firstName.text = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
// setting the last name
lastName.text = (NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty);
// setting the street name
//ABMultiValueRef street = ABRecordCopyValue(person, kABPersonAddressProperty);
// street.text = (NSString *)ABRecordCopyValue(person, kABPersonAddressStreetKey);
// setting the number
/*
this function will set the first number it finds
if you do not set a number for a contact it will probably
crash
*/
ABMultiValueRef multi = ABRecordCopyValue(person, kABPersonPhoneProperty);
number.text = (NSString*)ABMultiValueCopyValueAtIndex(multi, 0);
// remove the controller
//[self dismissModalViewControllerAnimated:YES];
return YES;
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier {
// Only inspect the value if it's an address.
if (property == kABPersonAddressProperty) {
/*
* Set up an ABMultiValue to hold the address values; copy from address
* book record.
*/
ABMultiValueRef multi = ABRecordCopyValue(person, property);
// Set up an NSArray and copy the values in.
NSArray *theArray = [(id)ABMultiValueCopyArrayOfAllValues(multi) autorelease];
// Figure out which values we want and store the index.
const NSUInteger theIndex = ABMultiValueGetIndexForIdentifier(multi, identifier);
// Set up an NSDictionary to hold the contents of the array.
NSDictionary *theDict = [theArray objectAtIndex:theIndex];
// Set up NSStrings to hold keys and values. First, how many are there?
const NSUInteger theCount = [theDict count];
NSString *keys[theCount];
NSString *values[theCount];
// Get the keys and values from the CFDictionary. Note that because
// we're using the "GetKeysAndValues" function, you don't need to
// release keys or values. It's the "Get Rule" and only applies to
// CoreFoundation objects.
[theDict getObjects:values andKeys:keys];
// Set the address label's text.
NSString *address;
address = [NSString stringWithFormat:#"%#, %#, %#, %# %#",
[theDict objectForKey:(NSString *)kABPersonAddressStreetKey],
[theDict objectForKey:(NSString *)kABPersonAddressCityKey],
[theDict objectForKey:(NSString *)kABPersonAddressStateKey],
[theDict objectForKey:(NSString *)kABPersonAddressZIPKey],
[theDict objectForKey:(NSString *)kABPersonAddressCountryKey]];
self.addressLabel.text = address;
// Memory management.
[theDict release];
// Return to the main view controller.
[ self dismissModalViewControllerAnimated:YES ];
// return Yes;
}
// If they didn't pick an address, return YES here to keep going.
return YES;
}
/*
// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// Custom initialization
}
return self;
}
*/
/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
}
*/
/*
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
}
*/
/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
}
#end
In
-[ABPeoplePickerNavigationControllerDelegate peoplePickerNavigationController:shouldContinueAfterSelectingPerson:property:identifier:]
you need to return NO in order not to launch Google Maps. Returning YES would continue with the default action, which on the device is launching Google Maps.