Wednesday 26 September 2012

Building advanced RSS reader with iOS6


In this tutorial you are going to learn:
  • how to separate the logic and have standalone RSS loader class which communicates to a delegate class
  • how to start customizing a table view
  • how to use rich text in table cells
  • how to show different content inside the table view depending on the state of the table
  • how to use a cool and sexy XML library called RaptureXML
Let’s start!

Project setup and initial libraries

Open up Xcode and create a new project. Choose the Master-Detail Application project template. Call the new application “ARSSReader”. Make sure “Use Storyboards” and “Use Automatic Reference Counting” checkboxes are turned on.
Download this ZIP file with several resource I pre-bundled for you: ! ARSS_files.zip.
When you extract the files from the ZIP file just drag them all into your project file list, and in the “Add files” dialogue be sure to check the checkbox to copy the files over to your project. You should see the files now in your project list like so:
  1. arss_header.png is just an image file you are going to use for the screen showing the list of articles fetched from the RSS feed.
  2. GTMNSString+HTML is a library from Google, which will convert html encoded entities to nice looking strings (ie. things like ‰ to ‘‘)
  3. And finally RaptureXML is an easy to use XML library, which you will use to read the list of RSS articles
You need to do a little bit of setup for all libs to work properly, so let’s go. First Click on the Xcode project file in the project file list, then select the “Build Phases” tab in the inner part of Xcode’s window, and unfold the “Compile sources” strip. Select “GTMNSString+HTML.m” file and press the Enter key on your keyboard. In the little popup window enter “-fno-objc-arc” and hit Enter again.
This will turn off ARC for that file (since Google didn’t update that library since 2008, it does not actually use any modern features).
While you are on that screen in Xcode, unfold as well the “Link Binary With Libraries” and press the “+” button. From the list that pops up select “libxml2.dylib” and “libz.dylib” to be added to your project.
Next switch to the “Build settings” tab and in the search field enter “Header Search Paths”. You will see then the Header Search Paths field, so double click on its value and add a new search path: “$(SDK_DIR)”/usr/include/libxml2″ (without the quites).
Alright! All the setup is done! If you hit Cmd+B your project should compile without any errors.

Code cleanup

Click on the MainStoryboard.storyboard file to see the default UI XCode has pre-built for you.
On screen 1 – you will show the list of articles in the RSS feed, and when the user taps on a title, he’ll be taken to screen 2 – where the page of the original article will be shown in a web view control.
OK, now open up “MasterViewController.m” as you will have to do some cleanup. Remove the following methods that Xcode inserted for you:
  • - (void)awakeFromNib
  • - (void)viewDidLoad
  • - (void)didReceiveMemoryWarning
  • - (void)insertNewObject:(id)sender
  • - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
  • - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
  • Also remove all the method which were inserted inside comments.
OK, now you have the view controller clean and ready for you to add some awesomeness! :)
For you class you will need two class members – the url of the RSS feed and the refresh control for the table view. Scroll to the top of the file and find where “NSMutableArray *_objects;” declaration is and replace it with these few variables (still within the enclosing curly brackets):
NSArray *_objects;
NSURL* feedURL;
UIRefreshControl* refreshControl;
Now inside the class body add this new viewDidLoad method:
- (void)viewDidLoad
{
    [super viewDidLoad];
 
    //configuration
    self.title = @"Advanced RSS Reader";
    feedURL = [NSURL URLWithString:@"http://feeds.feedburner.com/TouchCodeMagazine"];
 
}
You set the navigation bar title and also setup the url of the RSS feed. If you want to fetch different feed than the one this tutorial uses, replace it here. Let’s create the refresh control for the table view (so that the user is able to pull down the list to refresh it). Still in viewDidLoad add:
//add refresh control to the table view
refreshControl = [[UIRefreshControl alloc] init];
 
[refreshControl addTarget:self
                   action:@selector(refreshInvoked:forState:)
         forControlEvents:UIControlEventValueChanged];
 
NSString* fetchMessage = [NSString stringWithFormat:@"Fetching: %@",feedURL];
 
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:fetchMessage
    attributes:@{NSFontAttributeName:[UIFont fontWithName:@"Helvetica" size:11.0]}];
 
[self.tableView addSubview: refreshControl];
First you create a refresh control and you set its delegate method: refreshInvoked:forState: (you’ll have to implement this one in your class). Then you also setup the attributedTitle property of the refresh control to show the URL of the feed being downloaded. Finally you add it to the table view.
That’ll set all titles and the top bar. But you will want to have your table a little bit fancier. You’ll create a custom table header quickly.

Custom table header

Create a new file by choosing from the menu File/New/File … and than iOS/Cocoa Touch/Objective-C class, call the class TableHeaderView and make it inherit UIImageView.
Open up TableHeaderView.h and replace everything inside with this new interface declaration:
#import <UIKit/UIKit.h>
 
@interface TableHeaderView : UIImageView
- (id)initWithText:(NSString*)text;
- (void)setText:(NSString*)text;
@end
The table header will show the title of the RSS feed, so you need 2 methods – one initializer to create an instance with a given title, and one method to set the title after creation time. Now go on to TableViewHeader.m and replace all the code inside the file with:
#import "TableHeaderView.h"
 
@interface TableHeaderView()
{
 UILabel* label;
}
@end
 
@implementation TableHeaderView
 
- (id)initWithText:(NSString*)text
{
 UIImage* img = [UIImage imageNamed:@"arss_header.png"];
    if ((self = [super initWithImage:img])) {
        // Initialization code
  label = [[UILabel alloc] initWithFrame:CGRectMake(20,10,200,70)];
  label.textColor = [UIColor whiteColor];
  label.shadowColor = [UIColor grayColor];
  label.shadowOffset = CGSizeMake(1, 1);
  label.backgroundColor = [UIColor clearColor];
  label.font = [UIFont systemFontOfSize:20];
  label.text = text;
  label.numberOfLines = 2;
  [self addSubview:label];
    }
    return self;
}
 
- (void)setText:(NSString*)text
{
 label.text = text;
}
 
@end
As you can see from this very simple code – TableHeaderView inherits from UIImageView, therefore it can display an image (arss_header.png) and if you look at the initializer it also automatically creates one sub-child – a label to show the title of the RSS feed. Pretty easy.
Let’s use this new header back in MasterViewController.m, scroll to the top and below the other imports add:
#import "TableHeaderView.h"
Cool – scroll back down to viewDidLoad and at the bottom of the method body add:
//add the header
self.tableView.tableHeaderView = [[TableHeaderView alloc] initWithText:@"fetching rss feed"];
You can run the project now and have a look at the table header:
You can also pull down the list to crash the app – it’s OK as you still didn’t implement the table refresh method. Actually viewDidLoad and the frefresh control are going to invoke the same feed refresh method, so let’s go ahead and add it now:
//ADD at the end of viewDidLoad
[self refreshFeed];
 
//ADD as a separate method
-(void) refreshInvoked:(id)sender forState:(UIControlState)state {
    [self refreshFeed];
}
So apparently this mystical refreshFeed method is going to do the heavy work! You actually are going to create a separate class to take care to load the RSS feed, so let’s implement it first and then you’ll get back to refreshFeed.

Fetching RSS from the Internet

Create a new Objective-C class and call it RSSLoader, make it inherit NSObject. Create also another class called RSSItem, and make it inherit NSObject as well.
RSSLoader will fetch the xml feed, parse through and create an instance of the RSSItem model for each RSS entry.
Open up RSSItem.h and replace everything inside with this piece of code:
#import <Foundation/Foundation.h>
 
@interface RSSItem : NSObject
 
@property (strong, nonatomic) NSString* title;
@property (strong, nonatomic) NSString* description;
@property (strong, nonatomic) NSURL* link;
@property (strong, nonatomic) NSAttributedString* cellMessage;
 
@end
You will be fetching the title, description (these two are strings) and the URL of each RSS article, and you will store them in an instance of this class. Also RSSItem will generate the text to show in the table rows – you would be able to get this message via the cellMessage property. However this is everything you need to do for the RSSItem class right now.
Open up RSSLoader.h. Your RSSLoader class will feature only one method, which basically will fetch the RSS feed and callback a block of code. Inside the @interface declaration add the method:
-(void)fetchRssWithURL:(NSURL*)url complete:(RSSLoaderCompleteBlock)c;
The method takes in a URL and when it fetches the RSS feed at that URL it executes the block provides as a parameter as well. You will need to define the RSSLoaderCompleteBlock block type, so scroll up and under the import line add also this definition:
typedef void (^RSSLoaderCompleteBlock)(NSString* title, NSArray* results);
As you can see the fetchRssWithURL:complete: will provide back the title of the feed and an array of articles (RSSItem objects). Great.
Let’s go with the implementation of this – open RSSLoader.m. Add two imports at the top:
#import "RXMLElement.h"
#import "RSSItem.h"
 
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
RXMLElement is the RaptureXML class that handles a single XML tag. You will need it to parse the incoming feed. You also define a handy shortcut to getting a background queue – kBgQueue. The heavy load work of fetching content from the Internet you will need to do in the background, so you don’t block your app’s UI or other more important things, that the app need to execute simultaneously.
The method code is quite simple (with the help of RaptureXML), let’s start with this code:
-(void)fetchRssWithURL:(NSURL*)url complete:(RSSLoaderCompleteBlock)c
{
    dispatch_async(kBgQueue, ^{
 
        //work in the background
        RXMLElement *rss = [RXMLElement elementFromURL: url];
        RXMLElement* title = [[rss child:@"channel"] child:@"title"];
        NSArray* items = [[rss child:@"channel"] children:@"item"];
 
        NSMutableArray* result = [NSMutableArray arrayWithCapacity:items.count];
 
        //more code
    });
 
}
dispatch_async executes the provided block in a background thread, so you’re safe for doing simultaneous work. [RXMLElement elementFromURL:url] is the single line, which fetches the XML feed from your server, parses it, and loads it up in the rss object. Awesome! Next you fetch the “title” child of the “channel” element and store it in the object called title. Accessing the XML hierarchy is pretty easy, right? Finally you grab all “item” tags found inside the “channel” tag and store them in the items array.
Now where the “//more code” comment is add this code, which will turn items into a list of RSSItem objects and pass them back to the complete block:
for (RXMLElement *e in items) {
 
    //iterate over the articles
    RSSItem* item = [[RSSItem alloc] init];
    item.title = [[e child:@"title"] text];
    item.description = [[e child:@"description"] text];
    item.link = [NSURL URLWithString: [[e child:@"link"] text]];
    [result addObject: item];
}
 
c([title text], result);
You loop over the RXMLElement objects inside the items array and for each element you create a new RSSItem object, assign the title tag’s text to its title property, assign the description tag’s text, create an NSURL out of the element’s link tag, and finally you just add the RSSItem instance to the result array.
After you finish preparing the RSSItem list you call the “c” block and pass to it the feed’s title and the list of articles.
I promised it’d be easy, right?
Now let’s connect everything together. Open up again MasterViewController.m and under the other imports add these two as well:
#import "RSSLoader.h"
#import "RSSItem.h"
Add inside the implementation body also the refreshFeed:
-(void)refreshFeed
{
    RSSLoader* rss = [[RSSLoader alloc] init];
    [rss fetchRssWithURL:feedURL
                complete:^(NSString *title, NSArray *results) {
                    //completed fetching the RSS
                    dispatch_async(dispatch_get_main_queue(), ^{
                        //UI code on the main queue
 
                    });
                }];
}
Let’s see what the method does. You create a new RSSLoader instance and call it simply rss. Then you call your awesome fetchRssWithURL method.
In the block you dispatch a task for the main queue (because you need to update the UI when the results are in). Inside the main queue block there’s just a comment “UI code on the main queue” go ahead and replace it with the actual code:
[(TableHeaderView*)self.tableView.tableHeaderView setText:title];
 
_objects = results;
[self.tableView reloadData];
 
// Stop refresh control
[self.refreshControl endRefreshing];
First of all you update the feed title – displayed in the table header, by your custom class.
Then you store the results in the _objects array and call reloadData on the table view. That should show the articles in the table.
In the end you call endRefreshing on the refreshControl, so if the refresh control is visible it’ll hide.
OK, pretty good so far! And not complicated at all.
You can run again the project and see how far you’ve gone, wow!
You can already see the list of article titles from your RSS feed! Cool.


Happy iCoding.

No comments:

Post a Comment