RSSリーダに必要な基本機能である、フィードのダウンロード、XMLのパースまでが、前回で準備できた。今回は、アプリケーションの仕上げを行おう。フィードから記事情報を取り出して、それを画面に表示するのだ。

RSSアイテムの取得

ここで作成しているアプリケーションは、XMLのパースはlibxmlのSAXパースを使っている。これは前回までに解説したように、パフォーマンスを最大にするためだ。だが正直に言えば、RSSフィードのXMLから記事情報を取り出すことを考えると、SAXよりもDOMパーサを使った方が楽だ。しかし、ここではあえて、SAXを使い続ける事にする。

SAXのようなドキュメントの先頭から終わりまでを順々になめていくパーサを使って、RSSのような構造を持つデータをパースするときは、フラグを適切に用意してやる事が大切だ。現在チャンネル情報をパースしているのか、アイテム情報をパースしているのか、を表すフラグだ。また、パース済みのアイテム情報を確保しておくためのインスタンス変数も必要になる。

これらを、DownloadOperationクラスに追加しよう。次のようになる。

List 1.

@interface DownloadOperation : NSOperation
{
    ...

    BOOL                    _isChannel, _isItem;
    NSMutableDictionary*    _channel;
    NSMutableDictionary*    _currentItem;
    NSMutableString*        _currentCharacters;
}

// Property
@property (readonly) NSDictionary* channel;

...

@end

_isChannelと_isItemが、それぞれパース中の項目を表すフラグだ。_currentItem、_currentCharactersは、現在パース中の情報を一時的に参照するためのものになる。パースされたアイテム情報は、_channelの中に入れられる。

アイテムやチャンネルには、タイトル、リンク、テキストといった情報がある。これらの管理には、NSDictionaryクラスを使おう。わざわざ専用クラスを作るまでもないだろう。

パースは、libxmlハンドラで行う。まず、フラグの操作を紹介しよう。要素開始ハンドラでフラグを設定し、要素終了ハンドラでフラグをクリアする。フラグを設定するところのソースコードは、次のようになる。

List 2.

- (void)startElementLocalName:(const xmlChar*)localname
        prefix:(const xmlChar*)prefix
        URI:(const xmlChar*)URI
        nb_namespaces:(int)nb_namespaces
        namespaces:(const xmlChar**)namespaces
        nb_attributes:(int)nb_attributes
        nb_defaulted:(int)nb_defaulted
        attributes:(const xmlChar**)attributes
{
    // channel
    if (strncmp((char*)localname, "channel", sizeof("channel")) == 0) {
        // フラグを設定する
        _isChannel = YES;

        return;
    }

    // item
    if (strncmp((char*)localname, "item", sizeof("item")) == 0) {
        // フラグを設定する
        _isItem = YES;

        // itemを作成する
        _currentItem = [NSMutableDictionary dictionary];
        [[_channel objectForKey:@"items"] addObject:_currentItem];

        return;
    }

    ...

要素終了ハンドラでは、title、link、description要素の場合、それらを確保しておく。このとき、フラグを利用して、チャンネル情報なのかアイテム情報なのかを決定する。

List 3.

- (void)endElementLocalName:(const xmlChar*)localname
        prefix:(const xmlChar*)prefix URI:(const xmlChar*)URI
{
    ...

    // title, link, description
    if (strncmp((char*)localname, "title", sizeof("title")) == 0 ||
        strncmp((char*)localname, "link", sizeof("link")) == 0 ||
        strncmp((char*)localname, "description", sizeof("description")) == 0)
    {
        // キー文字列を作成する
        NSString*   key;
        key = [NSString stringWithCString:(char*)localname
                encoding:NSUTF8StringEncoding];

        // 辞書を決定する
        NSMutableDictionary*    dict = nil;
        if (_isItem) {
            dict = _currentItem;
        }
        else if (_isChannel) {
            dict = _channel;
        }

        // 文字列を設定する
        [dict setObject:_currentCharacters forKey:key];
        [_currentCharacters release], _currentCharacters = nil;
    }
}

これで、チャンネル情報およびアイテム情報を取得できた。

カスタムセルの作成

次は取得した情報を画面に表示するのだが、これにはテーブルビューを使う事にしよう。テーブルビューでは、テーブルビューセルを使って表示を行う。ここでは、チャンネル情報、アイテム情報を表示するための、カスタムセルを作成してみよう。それぞれ、RSSChannelCell、RSSItemCellという名称にする。これらは、UITableViewCellクラスを継承することになる。

まず、チャンネルセルを作ろう。チャンネルセルでは、チャンネル名と、アイテムの数を表示するようにする。これらは、プロパティを使ってアクセスしよう。さらに、これらを表示するためのラベルを用意する。これにより、RSSChannelCellの定義は次のようになる。

List 4.

@interface RSSChannelCell : UITableViewCell
{
    UILabel*    _titleLabel;
    UILabel*    _numberOfItemsLabel;
}

// Property
@property (nonatomic, retain) NSString* title;
@property (nonatomic) int numberOfItems;

@end

_titleLabelと_numberOfItemsLabelという2つのラベルを用意した。これらに表示する内容は、プロパティのtitle、numberOfItemsでアクセスする。

後は、このラベルを、セルの内部に適切に配置してやればいい。これには、layoutSubviewsメソッドを使う。このメソッドを上書きする事で適切なタイミングで再配置を行うことができる。次のようなソースコードになるだろう。

List 5.

- (void)layoutSubviews
{
    // 親クラスのメソッドを呼び出す
    [super layoutSubviews];

    // 大きさを取得する
    CGRect  bounds, rect;
    bounds = self.bounds;

    // タイトルラベルをレイアウトする
    rect.origin.x = 8.0f;
    rect.origin.y = 8.0f;
    rect.size.width = CGRectGetWidth(bounds) - 16.0f;
    rect.size.height = 16.0f;
    _titleLabel.frame = rect;

    // アイテム数ラベルをレイアウトする
    rect.origin.x = 8.0f;
    rect.origin.y = 26.0f;
    rect.size.width = CGRectGetWidth(bounds) - 16.0f;
    rect.size.height = 14.0f;
    _numberOfItemsLabel.frame = rect;
}

RSSItemCellも同様に作ることになる。

コントローラの構成

これで準備が整った。後は、アプリケーションとして構成し直すだけである。

iPhoneアプリケーションは、ビューコントローラを基本として構成される。今回は、チャンネル一覧を表すものと、アイテム一覧を表すものの、2つのビューコントローラを作成しよう。それぞれ、RSSChannelController、RSSItemControllerとする。これらはテーブルビューを管理して、そこに情報を表示する事にする。

この2つのビューコントローラは、ナビゲーションコントローラの中に入れて使用する事にしよう。これは、アプリケーション全体のコントローラとなる、RSSAppDelegateクラスで行う。ナビゲーションコントローラを作成する際に、ルートビューコントローラとしてRSSChannelControllerを指定する。

List 6.

- (void)applicationDidFinishLaunching:(UIApplication *)application
{
    // コントローラを作成する
    _channelController = [[RSSChannelController alloc] init];
    _navController = [[UINavigationController alloc]
            initWithRootViewController:_channelController];

    // ビューを追加する
    [_window addSubview:_navController.view];

    // ウィンドウを表示する
    [_window makeKeyAndVisible];
}

これで、起動時にRSSChannelControllerが表示される。

RSSChannelControllerでは、フィードのダウンロードの管理を行う。つまり、前回までに解説してきた、NSOperationQueueの管理や、ダウンロード完了通知の受け取りなどは、このクラスが行うことになる。ダウンロードが完了したら、順次テーブルビューに表示していく。テーブルが選択されたら、RSSItemControllerを作成して、それをナビゲーションコントローラにプッシュしてやる。

RSSItemControllerでは、アイテム一覧を表示する。アイテムが選択されたら、Safariを起動してそのページを開くようにしよう。

これでRSSリーダの完成だ。

さらにアプリケーションの完成度を高めるならば、フィードリストの編集や、未読記事数の表示などの機能が追加できるだろう。

ここまでのソースコード: RSS-3.zip