I’m pulling data from an API and filling a UITableView with article titles (from Article objects). I have that part working, as far as I can tell. Where I’m having a hard time is trying to show the details of the article when a row is touched on the table view.
I’m correctly capturing the article ID, and then I make a request out to the API to pull it’s data and do a very similar thing to what I’m doing to load the articles UITableView in the step before. Here’s my parser for getting article details:
// ArticleDetailParser.h
#import <Foundation/Foundation.h>
#import "Article.h"
@class Article;
@class ArticleDetailParser;
@protocol ArticleDetailParserDelegate <NSObject>
- (void)parserDidFinish:(ArticleDetailParser *)parser;
- (void)parser:(ArticleDetailParser *)parser didFailWithError:(NSError *)error;
@end
@interface ArticleDetailParser : NSObject <NSXMLParserDelegate> {
id<ArticleDetailParserDelegate> delegate;
NSMutableString *currentCharacters;
Article *currentArticle;
NSMutableArray *articlesCollection;
NSMutableData *xmlData;
NSURLConnection *connectionInProgress;
}
@property (nonatomic, assign) id<ArticleDetailParserDelegate> delegate;
- (void)parseUrl:(NSString *)url;
- (void)beginParsing:(NSURL *)xmlUrl;
- (Article *)detailedArticle;
@end
And here’s the implementation:
// ArticleDetailParser.m
#import "ArticleDetailParser.h"
@implementation ArticleDetailParser
@synthesize delegate;
#pragma mark -
#pragma mark Parsing methods
- (void)parseUrl:(NSString *)url
{
NSURL *xmlUrl = [NSURL URLWithString:url];
[self beginParsing:xmlUrl];
}
- (void)beginParsing:(NSURL *)xmlUrl
{
[articlesCollection removeAllObjects];
articlesCollection = [[NSMutableArray alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:xmlUrl cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
// clear existing connection if there is one
if (connectionInProgress) {
[connectionInProgress cancel];
[connectionInProgress release];
}
[xmlData release];
xmlData = [[NSMutableData alloc] init];
// asynchronous connection
connectionInProgress = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
}
- (Article *)detailedArticle
{
return [articlesCollection objectAtIndex:0];
}
#pragma mark -
#pragma mark NSXMLParserDelegate methods
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[xmlData appendData:data];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqual:@"article"]) {
currentArticle = [[Article alloc] init];
return;
}
if ([elementName isEqual:@"id"]) {
currentCharacters = [[NSMutableString alloc] init];
return;
}
if ([elementName isEqual:@"title"]) {
currentCharacters = [[NSMutableString alloc] init];
return;
}
if ([elementName isEqual:@"alphabetical_title"]) {
currentCharacters = [[NSMutableString alloc] init];
return;
}
if ([elementName isEqual:@"body"]) {
currentCharacters = [[NSMutableString alloc] init];
return;
}
if ([elementName isEqual:@"body_html"]) {
currentCharacters = [[NSMutableString alloc] init];
return;
}
if ([elementName isEqual:@"category"]) {
currentCharacters = [[NSMutableString alloc] init];
return;
}
if ([elementName isEqual:@"authors"]) {
currentCharacters = [[NSMutableString alloc] init];
return;
}
if ([elementName isEqual:@"last_updated"]) {
currentCharacters = [[NSMutableString alloc] init];
return;
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
[currentCharacters appendString:string];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqual:@"article"]) {
[articlesCollection addObject:currentArticle];
[currentArticle release], currentArticle = nil;
return;
}
if ([elementName isEqual:@"id"]) {
[currentArticle setArticleID:currentCharacters];
[currentCharacters release], currentCharacters = nil;
return;
}
if ([elementName isEqual:@"title"]) {
[currentArticle setTitle:currentCharacters];
[currentCharacters release], currentCharacters = nil;
return;
}
if ([elementName isEqual:@"alphabetical_title"]) {
[currentArticle setAlphabeticalTitle:currentCharacters];
[currentCharacters release], currentCharacters = nil;
return;
}
if ([elementName isEqual:@"body"]) {
[currentArticle setBody:currentCharacters];
[currentCharacters release], currentCharacters = nil;
return;
}
if ([elementName isEqual:@"body_html"]) {
[currentArticle setBodyHtml:currentCharacters];
[currentCharacters release], currentCharacters = nil;
return;
}
if ([elementName isEqual:@"category"]) {
[currentArticle setCategory:currentCharacters];
[currentCharacters release], currentCharacters = nil;
return;
}
if ([elementName isEqual:@"authors"]) {
[currentArticle setAuthors:currentCharacters];
[currentCharacters release], currentCharacters = nil;
return;
}
if ([elementName isEqual:@"last_updated"]) {
[currentArticle setLastModified:currentCharacters];
[currentCharacters release], currentCharacters = nil;
return;
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData];
[parser setDelegate:self];
[parser parse];
[parser release];
[delegate parserDidFinish:self];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[currentArticle release];
currentArticle = nil;
[currentCharacters release];
currentCharacters = nil;
[articlesCollection release];
articlesCollection = nil;
[connectionInProgress release];
connectionInProgress = nil;
[xmlData release];
xmlData = nil;
NSLog(@"connection failed: %@", [error localizedDescription]);
[delegate parser:self didFailWithError:error];
}
@end
Does anything stand out at you as being wrong? Here’s my controller where I’m making use of the ArticleDetailParser:
// ArticleDetailViewController.h
#import <UIKit/UIKit.h>
#import "ArticleDetailParser.h"
@class Article;
@interface ArticleDetailViewController : UIViewController <ArticleDetailParserDelegate> {
Article *article;
UILabel *aTitle;
UILabel *aCategory;
UILabel *aAuthors;
UIActivityIndicatorView *activityView;
}
@property (nonatomic, assign) Article *article;
@property (nonatomic, assign) UIActivityIndicatorView *activityView;
- (void)loadArticleDetail;
- (void)showArticle;
@end
And the implementation of said controller:
// ArticleDetailViewController.m
#import "ArticleDetailViewController.h"
@implementation ArticleDetailViewController
@synthesize article, activityView;
#pragma mark -
#pragma mark init and dealloc
- (id)init
{
[super initWithNibName:nil bundle:nil];
[[self navigationItem] setTitle:@"Article Details"];
article = [[Article alloc] init];
aTitle = [[UILabel alloc] initWithFrame:CGRectMake(20, 10, 280, 25)];
aCategory = [[UILabel alloc] initWithFrame:CGRectMake(20, 30, 280, 25)];
aAuthors = [[UILabel alloc] initWithFrame:CGRectMake(20, 50, 280, 25)];
activityView = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 25, 25)];
[[self activityView] sizeToFit];
[[self activityView] setAutoresizingMask:(UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin)];
return self;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
return [self init];
}
- (void)dealloc {
[article release];
[aTitle release];
[aCategory release];
[aAuthors release];
[activityView release];
[super dealloc];
}
#pragma mark -
#pragma mark Web Service methods
- (void)loadArticleDetail
{
NSString *urlToRequest = [NSString stringWithFormat:@"http://wvencyclopedia.org/articles/%@.xml", [[self article] articleID]];
ArticleDetailParser *aDetailParser = [[ArticleDetailParser alloc] init];
[aDetailParser setDelegate:self];
[aDetailParser parseUrl:urlToRequest];
[aDetailParser release];
}
#pragma mark -
#pragma mark ArticleDetailParserDelegate methods
- (void)parserDidFinish:(ArticleDetailParser *)parser
{
article = [parser detailedArticle];
[[self activityView] stopAnimating];
}
- (void)parser:(ArticleDetailParser *)parser didFailWithError:(NSError *)error
{
NSString *errorString = [NSString stringWithFormat:@"Fetch failed: %@", [error localizedDescription]];
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:errorString delegate:nil cancelButtonTitle:@"OK" destructiveButtonTitle:nil otherButtonTitles:nil];
[actionSheet showInView:[[self view] window]];
[actionSheet autorelease];
}
#pragma mark -
#pragma mark UIView methods
- (void)showArticle
{
[aTitle setText:[[self article] title]];
[[self view] addSubview:aTitle];
[aTitle release];
[aCategory setText:[[self article] category]];
[[self view] addSubview:aCategory];
[aCategory release];
[aAuthors setText:[[self article] authors]];
[[self view] addSubview:aAuthors];
[aAuthors release];
}
#pragma mark -
#pragma mark UIViewController methods
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self loadArticleDetail];
[[self activityView] startAnimating];
[self showArticle];
}
- (void)viewDidLoad {
[super viewDidLoad];
[[self view] setBackgroundColor:[UIColor groupTableViewBackgroundColor]];
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)loadView
{
[super loadView];
UIBarButtonItem *loadingView = [[UIBarButtonItem alloc] initWithCustomView:[self activityView]];
[[self navigationItem] setRightBarButtonItem:loadingView];
}
#pragma mark -
#pragma mark Memory Management
- (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.
}
@end
This is my first real iPhone application outside of tutorials in books, and I’m new to Objective-C as well, so I could easily be misunderstanding the memory management aspects, as well as getting labels on the view.
What’s happening is I click on the table view to load an article, and for the first one it will display the article title only. Then I go back, try to click another and it crashes.
I’m sorry to post this much code, but I figured if someone chooses to help it might be best to have the whole picture.
Thanks SO much in advance, if you should take on such a task!
The reason your app is crashing could be due to your UILabels getting deallocated when you release them in showArticle:.
You’ll want to use
@property (nonatomic, retain)instead of
@property (nonatomic, assign)when dealing with objects (variable types that require the star in front of the name). Use
assignfor primitive types like integers and doubles.