2010-11-28

表示するブログの選択 - 環境設定パネルを作る #1 (MacBloggerGlass)

GAE 版 Blogger Glass では表示するブログの Blog ID を config.yaml という設定ファイルで指定するようになっている。また、実行時にユーザが(Google アカウント)でログインすることで、ユーザごとに Blog ID を指定できるように「Settings」画面も用意した(→「GAE アプリでユーザごとの設定を可能にする #5」)。

Mac 版では表示するブログの選択は「環境設定パネル(Preferences)」を開いて行う。GAE 版のように Blog ID を直接指定するのはメンドウなので、Google アカウントの ID をパスワードを入力することで、そのアカウントで作ったブログの一覧から選べるようにしたい。

今回は、そんな「環境設定パネル」でブログ一覧を取得し、表示するところまでを作り込む。

環境設定パネルを追加する

Cocoa アプリに対する「環境設定パネル」の設置手順は「ヒレガス本」の Chapter 12 〜 14 の内容となる。

ざっくり手順を並べると以下のようになる。

  1. 「環境設定パネル」を制御するコントローラを、NSWindowController を継承して作る。
  2. 適当な場所(AppContoller みたいなものを作っていればそこ)に「環境設定パネル」を表示させるアクションを作る。
  3. Interface Builder でメインメニューのメニュー項目(「アプリ名」>「Preferences...」)に「環境設定パネル」を表示させるアクションを接続する。
  4. 「環境設定パネル」用の NIB ファイルを作る(実際には XIB ファイル)。この NIB の File's Owner は「環境設定パネル」を制御するコントローラにする。
  5. Interface Builder でパネルを作る。
  6. 「環境設定パネル」を制御するコントローラに、パネルで使うアクション等を作り込む。
  7. 「環境設定パネル」の設定を User Defaults に書き込むようにする。
  8. User Defaults の値を参照するように、メインのアプリウィンドウのコードを変更する。

今回の記事の内容は、このうちの 6. までとなっている。7. と 8. は次回に持ち越し。

デザイン (意匠)

環境設定パネルの外観と簡単な使い方を以下に示す。

MacBloggerGlass ウィンドウデザイン
環境設定パネル
Google Account の ID (E-mailアドレス) とパスワードを入力し「Get Blog List」を押すと、下部の Blog for viewing にその Google アカウントで Blogger に作ったブログのタイトル一覧が現れる。アプリで表示させたいブログを選択し、「Save」ボタンを押して保存する。パネルを閉じるには左上のクローズボタンを押すか、ESC キーを叩く。

デザイン (設計)

Google アカウントの ID とパスワードを入力させて、ブログの一覧を取得するという機能は、GData ライブラリのパッケージにふくまれている Blogger 用サンプルにほぼそのままの形で作り込まれている。それをのまま流用しても良かったが(実際、GData を使った Google Data API へのアクセスの部分は流用している)、ここでは Cocoa Bindings を使ってみたい。つまり、「環境設定パネル」のコントローラ(PreferenceController クラスとした)を NSTableView のデータソースとするのではなく、モデルとなるオブジェクトを NSObjectController (あるいはその派生クラスたち)を介して、NSTableView と結びつけるようにしたい、ということ。

後述するように、モデルは、ブログごとの情報を抱えるオブジェクトと、その配列を抱えるオブジェクトの二段構えになっている。これは、Google Data API を経由してユーザのブログ一覧を取得した際に、GData ライブラリが生成するオブジェクトの構成をそのまま写し取ったものだ。

(GData ライブラリによるブログ一覧)
+---------------+
| GDataFeedBlog |
|---------------|            +----------------+
|- entries    --|----------->| GDataEntryBlog |
|  ...          | 1        * |----------------|
|               |            |  ...           |
+---------------+            +----------------+

GDataEntryBlog は個々のブログの ID やらタイトルやらといった情報を保持している。

この二段構えのモデル構造は以前の記事(→「Cocoa Bindings の使い方」)でダミーとして作ったモデルの構造と同じだ。あのサンプルアプリでは、二段構えモデルに対応するため、Cocoa Bindings のためのコントローラも NSObjectController と NSArrayController の二段構えとした。その構成をそのまま、今回の「環境設定パネル」でも用いる。

実装

モデル

モデルクラスは 2 つ。FeedBlog クラスは GData ライブラリが提供する GDataFeedBlog に対応するもので、EntryBlog クラスは同じく GDataEntryBlog に対応するものだ。どちらも対応する GData ライブラリのクラスを内部に抱える(has-a の関係)とともに、ビューで表示するための情報を抽出するためのクラスとして作った。抽出した情報(例: ブログのタイトル)は、主にビューから Cocoa Bindings の結びつきを介してアクセスするために用いる。

どちらのクラスにも、対応する GData ライブラリのオブジェクトをセットするためのメソッドを用意していて、そこで情報の抽出も行っている。

FeedBlog
//
//  FeedBlog.h
//  MacBloggerGlass

#import <Cocoa/Cocoa.h>
@class GDataFeedBlog;

@interface FeedBlog : NSObject {
    NSMutableArray *entries;
    GDataFeedBlog *gDataFeed;
}

@property (readonly) NSMutableArray *entries;
@property (readwrite, retain) GDataFeedBlog *gDataFeed;

@end
//
//  FeedBlog.m
//  MacBloggerGlass

#import "GData/GDataBlogger.h"
#import "FeedBlog.h"
#import "EntryBlog.h"

@implementation FeedBlog
- (id)init
{
    [super init];
    entries = [[NSMutableArray alloc] init];
    return self;
}

- (void)dealloc
{
    [gDataFeed release];
    [entries release];
    [super dealloc];
}

@synthesize entries;
@synthesize gDataFeed;
- (void)setGDataFeed:(GDataFeedBlog *)aFeed
{
    if (aFeed == gDataFeed) return;
    [gDataFeed release];
    gDataFeed = [aFeed retain];

    [entries removeAllObjects];

    EntryBlog *blog;
    GDataEntryBlog *entry;
    for (entry in [gDataFeed entries]) {
        blog = [[EntryBlog alloc] init];
        blog.gDataEntry = entry;        
        [entries addObject:blog];
    }
    NSLog(@"%d blogs was found.", [entries count]);
}
@end

setGDataFeed: で GDataFeedBlog の抱える配列から GDataEntryBlog の要素を取り出している for (var in array) { ... } という書き方が Objective-C 2.0 から導入された高速列挙 (fast enumeration) だ。Ruby や Python ではお馴染みのもの。配列の添字でループし、objectAtIndex: で配列にアクセスするよりもスッキリと書ける。array の部分には配列(NSArray)以外の集合(NSSet)や辞書(NSDictionary)も指定できる。詳しくは荻原(2.0)本 CHAPTER 08 の p.202 〜 206 あたりを参照のこと。

EntryBlog
//
//  EntryBlog.h
//  MacBloggerGlass

#import <Cocoa/Cocoa.h>
@class GDataEntryBlog;

@interface EntryBlog : NSObject {
    NSString *title;
    NSString *blogID;
    GDataEntryBlog *gDataEntry;
}

@property (readonly) NSString *title, *blogID;
@property (readwrite, retain) GDataEntryBlog *gDataEntry;

@end
//
//  EntryBlog.m
//  MacBloggerGlass

#import "GData/GDataBlogger.h"
#import "EntryBlog.h"

@interface EntryBlog (PrivateMethods)
- (NSString *)extractBlogID:(NSString *)identifier;
@end


@implementation EntryBlog
- (void)dealloc
{
    [gDataEntry release];
    [blogID release];
    [title release];
    [super dealloc];
}

@synthesize title, blogID;
@synthesize gDataEntry;
- (void)setGDataEntry:(GDataEntryBlog *)aEntry
{
    if (aEntry == gDataEntry) return;
    [gDataEntry release];
    gDataEntry = [aEntry retain];

    title = [[[gDataEntry title] stringValue] retain];
    blogID = [[self extractBlogID:[gDataEntry identifier]] retain];
}
@end

@implementation EntryBlog (PrivateMethods)

#pragma mark -
#pragma mark PrivateMethods
- (NSString *)extractBlogID:(NSString *)identifier
{
    if (!identifier || [identifier length] == 0) return nil;

    // GDataEntryBlog.identifier is in the format
    // "tag:blogger.com,1999:user-<user id>.blog-<blog id>"
    NSRange rangeBlog = [identifier rangeOfString:@"blog-"];
    int startPos = rangeBlog.location + rangeBlog.length;

    return [identifier substringFromIndex:startPos];
}

@end

EntryBlog.m の方で @interface EntryBlog (PrivateMethods) ... @end として宣言しているのは、このファイルからのみ参照するためのメソッドの定義だ。C でいう static 関数のようなもの。カテゴリの使い方の 1 つ。これも詳しくは荻原(2.0)本 CHAPTER 09 の p.227 〜 228 を参照のこと。

コントローラ

先に述べたように Cocoa Bindings のためのコントローラには NSObjectController と NSArrayController の 2 つを組み合わせて使う。左のスクリーンショットで Feed Observer となっているものが NSObjectController (のインスタンス)、Array Observer となっているものが NSArrayController (のインスタンス) だ。インスタンス名を Observer としているのは、どちらも KVO (Key-Value Observing) のため(だけ)に使っているからだ。

PreferenceController

環境設定パネルを表示するためのクラスで、NSWindowController を継承している。

GData を使ってブログ一覧を取得する部分は以前の記事(→「記事一覧を取得する」)で示したものとほとんど同じなので省略する。

以下に示すのは、ブログ一覧を取得するために GData ライブラリのサービスを呼び出した際に指定するコールバックメソッドだ。注意してほしいのは 102 と 104 行目。これは、KVO で キーに対応する変更通知を発行するための「作法」の 1 つ。103 行目で、feed が保持するオブジェクト(前述の FeedBlog のインスタンス)が抱える配列の内容が変更される。そのことを、PreferenceController に(「feed」というキーで)結びついているオブジェクト(今回の場合、それは Feed Observer という名前の NSObjectController のインスタンス)に通知している。

- (void)blogListTicket:(GDataServiceTicket *)ticket
      finishedWithFeed:(GDataFeedBlog *)aFeed
                 error:(NSError *)error
{
    if (error) {
        NSLog(@"ERROR in fetching the blog list: %@", error);
        return;
    }
    [self willChangeValueForKey:@"feed"];
    feed.gDataFeed = aFeed;
    [self didChangeValueForKey:@"feed"];
}

監視するオブジェクトの変更通知を受け取った Feed Observer (NSObjectController) は、今度は自分自身に(「entries」というキーで)結びついているオブジェクト(Array Observer という名前の NSArrayController のインスタンス)に変更通知を送る。それを受け取った Array Observer は、NSTableView (の中の NSTableColumn) に変更通知を送る。NSTableView は KVC によって結びついたオブジェクトのプロパティ(EntryBlog の title)を取得して、表示を更新する。このようにして、モデルの変更が Cocoa Bindings によって結びついたビューに伝播してゆくのだ。

次の展開

ブログ一覧を取得して表示できるようになっているが、まだ肝心の「Save」ボタンが機能しない。当然、メインウィンドウ(アプリ本体)とも連携していない。現段階では環境設定のためのパネルが単独で動いているだけ。

なので、次は「Save」ボタン(に対応するアクション)の実装と、アプリ本体とのつなぎ部分を実装することになる。

参考文献

詳解 Objective-C 2.0
荻原 剛志
ソフトバンククリエイティブ ( 2008-05-28 )
ISBN: 9784797346800
Cocoa Programming for Mac OS X
Aaron Hillegass
Addison-Wesley Professional ( 2008-05-15 )
ISBN: 9780321503619

関連記事

0 件のコメント:

コメントを投稿