iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) (82 page)

Read iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) Online

Authors: Aaron Hillegass,Joe Conway

Tags: #COM051370, #Big Nerd Ranch Guides, #iPhone / iPad Programming

BOOK: iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides)
4.42Mb size Format: txt, pdf, ePub
 

Let’s double-check our work so far. In
ListViewController.m
, add the following log statement to
connectionDidFinishLoading:
.

 
- (void)connectionDidFinishLoading:(NSURLConnection *)conn
{
    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData];
    [parser setDelegate:self];
    [parser parse];
    xmlData = nil;
    connection = nil;
    [[self tableView] reloadData];
    
NSLog(@"%@\n %@\n %@\n", channel, [channel title], [channel infoString]);
}

Build and run the application. At the end of the console, you should see the log statement with valid values for the three strings. The data isn’t correct yet, but there should still be three blocks of text separated by a new line.

 

Now we will need to write the code for the leaves of the object tree represented by the XML – the
RSSItem
instances. Create a new
NSObject
subclass. Name it
RSSItem
. In
RSSItem.h
, give the item instance variables for its metadata and for parsing.

 
@interface RSSItem : NSObject

{
    NSMutableString *currentString;
}
@property (nonatomic, weak) id parentParserDelegate;
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *link;
@end
 

In
RSSItem.m
, synthesize these properties and set up the parsing code similar to what you did for
RSSChannel
.

 
@implementation RSSItem
@synthesize title, link, parentParserDelegate;
- (void)parser:(NSXMLParser *)parser
    didStartElement:(NSString *)elementName
       namespaceURI:(NSString *)namespaceURI
      qualifiedName:(NSString *)qualifiedName
         attributes:(NSDictionary *)attributeDict
{
    NSLog(@"\t\t%@ found a %@ element", self, elementName);
    if ([elementName isEqual:@"title"]) {
        currentString = [[NSMutableString alloc] init];
        [self setTitle:currentString];
    }
    else if ([elementName isEqual:@"link"]) {
        currentString = [[NSMutableString alloc] init];
        [self setLink:currentString];
    }
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)str
{
    [currentString appendString:str];
}
- (void)parser:(NSXMLParser *)parser
 didEndElement:(NSString *)elementName
  namespaceURI:(NSString *)namespaceURI
 qualifiedName:(NSString *)qName
{
    currentString = nil;
    if ([elementName isEqual:@"item"])
        [parser setDelegate:parentParserDelegate];
}
@end

Build the application to check for syntax errors.

 

In
RSSChannel.m
, put
RSSItem
into the object tree. At the top of this file, make sure to import the header for
RSSItem
.

 
#import "RSSItem.h"
@implementation RSSChannel
- (void)parser:(NSXMLParser *)parser
    didStartElement:(NSString *)elementName
       namespaceURI:(NSString *)namespaceURI
      qualifiedName:(NSString *)qualifiedName
         attributes:(NSDictionary *)attributeDict
{
    if ([elementName isEqual:@"title"]) {
        currentString = [[NSMutableString alloc] init];
        [self setTitle:currentString];
    }
    else if ([elementName isEqual:@"description"]) {
        currentString = [[NSMutableString alloc] init];
        [self setInfoString:currentString];
    }
    else if ([elementName isEqual:@"item"]) {
        // When we find an item, create an instance of RSSItem
        RSSItem *entry = [[RSSItem alloc] init];
        // Set up its parent as ourselves so we can regain control of the parser
        [entry setParentParserDelegate:self];
        // Turn the parser to the RSSItem
        [parser setDelegate:entry];
        // Add the item to our array and release our hold on it
        [items addObject:entry];
    }
}

Build and run the application. You should see log statements in the console that indicate the tree is being built. The last log statement in the console should have the correct data for the channel object, which looks something like this:

 

forums.bignerdranch.com
Books written by Big Nerd Ranch
 

Finally, we need to connect the channel and its items to the table view. In
ListViewController.m
, import the header file for
RSSItem
and fill out the two data source methods you temporarily implemented earlier.

 
#import "RSSItem.h"
@implementation ListViewController
- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section
{
    
return 0;
    return [[channel items] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    
return nil;
    UITableViewCell *cell = [tableView
                            dequeueReusableCellWithIdentifier:@"UITableViewCell"];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                      reuseIdentifier:@"UITableViewCell"];
    }
    RSSItem *item = [[channel items] objectAtIndex:[indexPath row]];
    [[cell textLabel] setText:[item title]];
    return cell;
}

Build and run the application. You should now see the titles of the last 20 posts in a table view. Also, take a good look at the console to see the flow of the parser and how the delegate role is passed around.

 
A quick tip on logging

In this application, you log a lot of data to the console. It would be easy to miss an important log statement. One way to catch important statements is to prefix the most important ones with an easily searchable token (like
xxx
), but that’s a quick-and-dirty fix.

 

A more elegant and useful option is to define a preprocessor macro that you can use to categorize your log statements. For example, in
Nerdfeed
, you can generate a ton of log statements for checking the input and output of your web service requests. You can also generate a ton of log statements for checking the logic in the rest of the application. When you are debugging
Nerdfeed
, it would be helpful to separate the web service-related statements from the others so that you can turn them on or off as needed.

 

While there are many ways to do this, here is the simplest one:

 
#define WSLog(...) NSLog(__VA_ARGS__)
 

This statement tells the compiler,

When you come across
WSLog
, see
NSLog
.

Save this statement in its own
.h
file and import it into your precompiled header (
Nerdfeed_Prefix.pch
). Then, when you want to log a web service-related statement in your code, use
WSLog
instead of
NSLog
, passing the exact same arguments. For example, in
ListViewController.m
, you could change the log statement in
connectionDidFinishLoading:
to the following:

 
WSLog(@"%@\n %@\n %@\n", channel, [channel title], [channel infoString]);

As long as
WSLog
is defined to
NSLog
, nothing will change. You will still see all of your log statements in the console. When you want to turn off the web service-related statements to concentrate on other areas, simply re-define
WSLog
to the following in its header file:

 
#define WSLog(...) do {} while(0)

Now any
WSLog
calls will be invisible to the compiler, so they will not appear in the console to distract you from your non-web service debugging. (Defining
WSLog
in this way means it will be optimized out by the compiler.)

 
UIWebView

In addition to its title, an
RSSItem
also keeps a link that points to the web page where the post lives. It would be neat if
Nerdfeed
could open up
Safari
and navigate to that page. It would be even neater if
Nerdfeed
could render that web page without having to leave
Nerdfeed
to open
Safari
. Good news ߝ it can using the class
UIWebView
.

 

Instances of
UIWebView
render web content. In fact, the
Safari
application on your device uses a
UIWebView
to render its web content. In this part of the chapter, you will create a view controller whose view is an instance of
UIWebView
. When one of the items is selected from the table view of
RSSItem
s, you will push the web view’s controller onto the navigation stack and have it load the link stored in the
RSSItem
.

 

Create a new
NSObject
subclass and name it
WebViewController
. In
WebViewController.h
, add a property (but not an instance variable) and change the superclass to
UIViewController
:

 
@interface WebViewController : NSObject
@interface WebViewController : UIViewController
@property (nonatomic, readonly) UIWebView *webView;
@end
 

In
WebViewController.m
, override
loadView
to create an instance of
UIWebView
as the view of this view controller. Also, implement the method
webView
to return that view.

 
@implementation WebViewController
- (void)loadView
{
    // Create an instance of UIWebView as large as the screen
    CGRect screenFrame = [[UIScreen mainScreen] applicationFrame];
    UIWebView *wv = [[UIWebView alloc] initWithFrame:screenFrame];
    // Tell web view to scale web content to fit within bounds of webview
    [wv setScalesPageToFit:YES];
    [self setView:wv];
}
- (UIWebView *)webView
{
    return (UIWebView *)[self view];
}
 

In
ListViewController.h
, add a new property to
ListViewController
.

 
@class WebViewController;
@interface ListViewController : UITableViewController
{
    NSURLConnection *connection;
    NSMutableData *xmlData;
    RSSChannel *channel;
}
@property (nonatomic, strong) WebViewController *webViewController;
- (void)fetchEntries;
@end

In
ListViewController.m
, import the header file and synthesize the property.

 
#import "WebViewController.h"
@implementation ListViewController
@synthesize webViewController;
 

In
NerdfeedAppDelegate.m
, import the header for
WebViewController
, create an instance of
WebViewController
, and set it as the
webViewController
of the
ListViewController
.

 
#import "WebViewController.h"
@implementation NerdfeedAppDelegate
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    ListViewController *lvc =
        [[ListViewController alloc] initWithStyle:UITableViewStylePlain];
    UINavigationController *masterNav =
        [[UINavigationController alloc] initWithRootViewController:lvc];
    WebViewController *wvc = [[WebViewController alloc] init];
    [lvc setWebViewController:wvc];
    [[self window] setRootViewController:masterNav];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

(Note that we are instantiating the
WebViewController
in the application delegate in preparation for the next chapter where we will use a
UISplitViewController
to present view controllers on the iPad.)

 

When the user taps on a row in the table view, we want the
WebViewController
to be pushed onto the navigation stack and the link for the selected
RSSItem
to be loaded in its web view. To have a web view load a web page, you send it the message
loadRequest:
. The argument is an instance of
NSURLRequest
that contains the URL you wish to navigate to. In
ListViewController.m
, implement the following table view delegate method:

 
- (void)tableView:(UITableView *)tableView
                didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 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];
    // 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 requst object with that URL
    NSURLRequest *req = [NSURLRequest requestWithURL:url];
    // Load the request into the web view
    [[webViewController webView] loadRequest:req];
    // Set the title of the web view controller's navigation item
    [[webViewController navigationItem] setTitle:[entry title]];
}
 

Build and run the application. You should be able to select one of the posts, and it should take you to a new view controller that displays the web page for that post.

 

Other books

Yom Kippur Murder by Lee Harris
Grey's Lady by Natasha Blackthorne
A Severed Head by Iris Murdoch
A Plea of Insanity by Priscilla Masters
Mahabharata: Vol. 5 by Debroy, Bibek
Cooper by Liliana Hart
Hidden by Derick Parsons, John Amy