ページ

2009年11月30日月曜日

NSTableView にカスタムビューを表示する (5)カスタムセルへ bindings経由でモデルオブジェクトを渡す

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: NSTableView にカスタムビューを表示する (4) カスタムセル続き

NSCell は通常一つのプロパティにバインドするようにできている。カスタムセルでは複数のプロパティを扱いたい。Bindings経由で複数プロパティを渡すには、そのプロパティを持つモデルオブジェクトそのものが渡せれば良い。

(例)Book.title ではなく Book 自体を渡す。そうすれば Book.author にもアクセスできる。

今回はこの課題を扱う。


サンプル作成

前回までのコードに手をいれていく。最初に Interface Builder を立ち上げて Bindings 設定を変更する。



カスタムセルを貼付けている NSTableView の NSTableColumn の Bindings パネルを開き、Model Key Pathを変更する。

前回は上のように特定のプロパティ(Homepage.title)を指していたのをやめ下のように空する。

こうするとプロパティではなくモデルクラス Hompage(のインスタンス)自体に Bindings される。※今回試すまでは Model Key Path が空にできるなんてしらなかった。ただ Controller Key と組み合わせてたんにオブジェクトを探す為のパス(文字列)を構成するだけに使われることを考えると動作することはあたりまえか。

こうするとカスタムセルには Homepage インスタンスが渡されるようになる。

続いて Hompage に copyWithZone: を実装する。
Homepage.m

@implementation Homepage

@synthesize title, image;

- (id)copyWithZone:(NSZone *)zone
{
Homepage* homepage = [[[self class] allocWithZone:zone] init];

homepage.image = [image copyWithZone:zone];
homepage.title = [title copyWithZone:zone];

return homepage;
}

@end


セルの内容を表示する時にその元となるデータはコピーされ、それが表示に使われる。仮に copyWithZone: を実装しないと次の例外が起きて落ちてしまう。


CustomCell[5953:10b] *** -[Homepage copyWithZone:]: unrecognized selector sent to instance 0x1377d0
CustomCell[5953:10b] An uncaught exception was raised




最後にセルで Homepage が取得できているか確認するために描画処理内にデバッグコードを入れておく。

CustomCell.m

- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
[[NSColor greenColor] set];
NSRectFill(cellFrame);
[super drawWithFrame:cellFrame inView:controlView];

Homepage* homepage = [self objectValue];
NSLog(@"homepage: title=%@, image=%@", homepage.title, homepage.image);
}



実行

サンプルを実行してみよう。

セルの描画は何もコードを付け足していないので見た目は前回のまま。



デバッグ出力はこう。
CustomCell[5876:10b] homepage: title=SAMPLE-0, image=sample.jpg
CustomCell[5876:10b] setObjectValue: (null)
CustomCell[5876:10b] setObjectValue:
CustomCell[5876:10b] homepage: title=SAMPLE-1, image=sample.jpg
CustomCell[5876:10b] setObjectValue: (null)
CustomCell[5876:10b] setObjectValue:
CustomCell[5876:10b] homepage: title=SAMPLE-2, image=sample.jpg


Homepage のインスタンスがカスタムセルに渡っているのがわかる。意図通りに動いた。


解説

サンプルコードの構成はこんな感じになっている。


NSArrayController を通じて Homepage インスタンスが NSTableColumn へ渡り、最終的に CustomCell へコピーが渡される。CutomCell から見ると何もすることなく自動的に表示対象のデータ objectValue (すなわち Homepageインスタンス)が渡されることになる。このおかげで CustomCell は表示だけに専念できる。




ソースコード

github からどうぞ
CustomCell at 20091130 from xcatsan's SampleCode - GitHub


参考

複製を作るためにNSCopyingプロトコルに準拠する

copyWithZone: の件で参考になった。
最初 copyWithZone: の戻りを次のように autorelease していたらプログラムが吹っ飛んでいた。

Homepage* homepage = [[[[self class] allocWithZone:zone] init] autorelease];

autoreleae してはいけない。

Homepage* homepage = [[[self class] allocWithZone:zone] init];


Mac Dev Center: NSCopying Protocol Reference


- - - - -
Bindings が使えると実装がぐっと楽になる。次回は取得した複数のプロパティをカスタムセルで表示(描画)する方法を考えてみよう。

2009年11月29日日曜日

今週のCocoa情報(11/29) - 今週気になった Cocoaプログラミング情報の紹介

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

Are instance variables set to nil by default in Objective-C? - Stack Overflow

インスタンス変数は初期化されるのか?の話題。
=> 初期化される(0 にセットされる)。
Mac Dev Center: The Objective-C Programming Language: Allocating and Initializing Objects


今週は Stack Overflow であまり気になった記事は無かったので代わりに Mac Dev Center で気になった情報を掲載しておく。



Mac Dev Center: Dock Tile Programming Guide: Creating a Dock Tile Plug-in

Mac OS X v10.6 から "Dock Tile Plug-in" なるものが導入された。

Starting in Mac OS X v10.6, you can customize an application’s Dock tile icon and menu when the application is not running. 
アプリが動いていない時の Dockアイコンとメニューがカスタマイズできるようになるらしい。後日調べてみたい。



Mac Dev Center: NSCache Class Reference

これも Mac OS X v10.6 から導入されたもの。名前の通りキャッシュを管理するクラス。後日調べてみたい。



Mac Dev Center: Caching and Purgeable Memory

大量データを扱う場合のメモリ管理についての話。先ほどの NSCache の効果的な使い方や Purgeable Memory(解放可能なメモリ?)の考え方が解説されている。これも後日調べてみたい。

Mac Dev Center: NSDiscardableContent Protocol Reference



Mac Dev Center: Cocoa Drawing Guide: Images

キャッシュつながりで NSImage のキャッシュの話。

10.6 で NSImage のキャッシュ関連メソッドの多くは Deprecated となった。
Mac Dev Center: NSImage Class Reference

2009年11月28日土曜日

Core Data: モデルからクラスを自動生成する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

以前、Core Data のデータモデル(.xcdatamodel) からクラスが生成できないものかと書いたが方法が分かった。以下 Xcode 3.2 での例。


データモデル定義

こんなモデルを定義してみた。

NSManagedObject 自動生成

新規ファイル作成で Managed Object Class を選択する。



次へ進む。

ここで目的のエンティティを選択する。

できた。


中身はこんな感じ。

Book.h
#import


@interface Book :  NSManagedObject
{
}

@property (nonatomic, retain) NSString * author;
@property (nonatomic, retain) NSString * title;

@end

Book.m
#import "Book.h"


@implementation Book 

@dynamic author;
@dynamic title;

@end

- - - - -
これは便利。今後使っていこう。








2009年11月27日金曜日

NSTableView にカスタムビューを表示する (4) カスタムセル続き

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: Cocoaの日々: NSTableView にカスタムビューを表示する(3) カスタムセルの作成

setDataCell:
前回のコメントで -[NSTableColumn setDataCell:] を使わないのかと指摘を受けた。

Mac Dev Center: NSTableColumn Class Reference

どちらかといえばこちらが正攻法か。試してみよう。


@interface TableViewController : NSObject {
 :
IBOutlet NSTableColumn* tableColumn;
}


NSTableColumn のアウトレットを用意しておき、InterfaceBuilder で接続しておく。


-(void)awakeFromNib
{
CustomCell* cell = [[[CustomCell alloc] init] autorelease];
[cell setEditable:NO];
[tableColumn setDataCell:cell];
}


起動時にそこへ CustomCell のインスタンスを渡してやる。

実行。

前回と同じ挙動。なお前回と違って CutomCell を NSTextfiledCell のサブクラスとしてDBの値を表示させている。

試しに copyWithZone: を実装してみた。

CutomCell.m

- (id)copyWithZone:(NSZone *)zone
{
NSLog(@"self=%@, copyWithZone:%@", self, zone);
CustomCell* cell = (CustomCell*)[super copyWithZone:zone];
return cell;
}


実行時の挙動を見ていると copyWithZone: はセルが選択された時だけ呼び出された。

CustomCell[4469:10b] self=<CustomCell: 0x13db50>, copyWithZone:(null)

self は最初に NSTableColumn へセットした CustomeCell のインスタンスを常に指す。コピーして使い回しているのがわかる。ただセルを選択しない限りは copyWithZone: は呼ばれない。ということは表示だけの時は CustomeCell インスタンス一つを使い回しているということか。

試しに描画メソッドに値を書き出してみる。

CustomCell.m

- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
[[NSColor greenColor] set];
NSRectFill(cellFrame);
[super drawWithFrame:cellFrame inView:controlView];

NSLog(@"self=%@, value=%@", self, [self stringValue]);
}


CustomCell[4488:10b] self=<CustomCell: 0x13db50>, value=SAMPLE-0
CustomCell[4488:10b] self=<CustomCell: 0x13db50>, value=SAMPLE-1
CustomCell[4488:10b] self=<CustomCell: 0x13db50>, value=SAMPLE-2

CustomCellのインスタンスは一つで、値だけが変わっている。
おーそういうことか。NSCellのインスタンス は NSTableColumn 内で使い回されるのか。

セルが選択された時に copyWithZone: でコピーされる理由がわからない。編集に備えて複製しているのだろうか?

2009年11月26日木曜日

Cocoaの日々: NSTableView にカスタムビューを表示する(3) カスタムセルの作成

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: NSTableView にカスタムビューを表示する(2) ひな形作成〜Cocoa Bindings

今回は NSCell のサブクラスを作り、それを NSTableView へ表示してみよう。

ビュー

新規にウィンドウを用意しそこへ NSTableView を貼付ける。前回のものは比較の為に残しておく。



NSTableView の delegate として TableViewController を指定しておく。


カスタムセル

NSCell のサブクラスを用意する。

CustomCell.h

@interface CustomCell : NSCell {

}

@end


試しに drawWithFrame:inView: をオーバーライドして、緑色で塗りつぶすコードを書いてみた。

CustomCell.m

@implementation CustomCell

- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
[[NSColor greenColor] set];
NSRectFill(cellFrame);
}
@end



NSTableViewDelegate

NSTableViewDelegate のうち -tableView:dataCellForTableColumn:row: を実装した。

TableViewController.m

- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
NSLog(@"tableView: %@", tableView);
NSLog(@"  tableColumn: %@", tableColumn);
NSLog(@"  row: %d", row);


CustomCell* cell = [[[CustomCell alloc] init] autorelease];
return cell;
}

呼び出し状況がわかるように NSLog を入れておいた。


実行



出た。

デバッガコンソールを見ていると -[NSTableViewDelegate tableView:dataCellForTableColumn:row:] は頻繁に呼び出されている。


NSTableView をスクロールするとこれから表示されようとする行(row)で呼び出されるし、マウスカーソルを上に載せても呼び出される。また tableColumn が null だった。


考察

今回は -[NSTableViewDelegate tableView:dataCellForTableColumn:row:] の呼び出し毎に NSCell を生成して返していた。何度も呼び出される事を考えると使い捨てではなく、あらかじめ NSCellをプールしておいて再利用する方が良いかもしれない。そういえば iPhone の UITableViewCell は再利用を前提とした作りになっていたっけ。ただ UITableViewCell は UIView のサブクラスなので、NSViewよりも軽い NSCellの場合はそこまで考えなくてもよいかもしれない。


なお NSCell のサブクラス化については Mac Dev Center に指針がある。
Mac Dev Center: Control and Cell Programming Topics for Cocoa: Subclassing NSCell


必要なら初期化メソッド(-initImageCell など)のオーバーライドすることやその他Target-Action、マウストッキング、描画を行う際にオーバライドすべきメソッドが紹介されている。また最後に copyWithZone: についての説明もあった。
If the subclass contains instance variables that hold pointers to objects, consider overriding copyWithZone: to duplicate the objects. The default version copies only pointers to the objects.
NSCell は複製されて使われることが多いということか。今後の実装で copyWithZone: を考慮することは覚えておこう。そういえば ADCのサンプル SourceView でもカスタムセルは copyWithZone: を実装していたっけ。


ソースコード

github からどうぞ。
CustomCell at 20091126 from xcatsan's SampleCode - GitHub

参考

NSTableView with custom cells - Stack Overflow

2009年11月25日水曜日

NSTableView にカスタムビューを表示する(2) ひな形作成〜Cocoa Bindings

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: NSTableView にカスタムビューを表示する(調査中)

調査用にサンプルプログラムを作る。今回はテーブルビューにテストデータを表示するだけのプロジェクトを作成してみた。

クラス作成

Xcodeを立ち上げテンプレートから Cocoa Application を選んでプロジェクトを新規に作成する(Xcode3.1 / MacOSX10.5 で作成)。

まずコントローラーを作る。このクラスは表示用のデータ(配列)を保持している。初期化時にテストデータを生成して配列へ詰めておく。

TableViewController.h

@interface TableViewController : NSObject {

NSMutableArray* list;
}
@property (assign) NSMutableArray* list;

@end


TableViewController.m

- (id)init
{
self = [super init];
if (self) {
list = [[NSMutableArray alloc] init];

Homepage* homepage;

for (int i=0; i < 10; i++) {
homepage = [[[Homepage alloc] init] autorelease];
homepage.title = [NSString stringWithFormat:@"SAMPLE-%d", i];
homepage.image = @"sample.jpg";
[list addObject:homepage];
}
}
return self;
}



UI作成

次に InterfaceBuilder を起動しウィンドウへ NSTableView を貼付ける。

TableViewController と NSArrayController をインスタンス化しておく。

NSArrayController は NSTableView(のカラム)と TableViewController の間でのデータの橋渡しの役割を果たす。

カラムのバインディング設定で NSArrayController へバインドする。

一方、NSArrayController は TableViewController の list(NSMutableArray)へバインドする。

これでMVCが構成される。
 TableViewController.list <==> NSArrayController <==> NSTableViewColumn

 (Model)          (Controller)    (View)

(参考)Cocoaの日々 - 2005年8月


最終的な NSArrayController のバインド状態は下図のようになる。


実行

実行してみよう。

出た。


ソースコード

GitHub からどうぞ。
CustomCell at 20091125 from xcatsan's SampleCode - GitHub


- - - -
これをベースにしてカスタムビューを表示する検証を進める。

2009年11月24日火曜日

NSTableView にカスタムビューを表示する(調査中)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

NSTableView にカスタムビューを表示する方法を調査中。

NSTableView with custom cells - Stack Overflow

-tableView:dataCellForTableColumn:row: を使う方法が示唆されていた。

-tableView:dataCellForTableColumn:row: は NSTableViewDelegate プロトコルで定義されている。

Mac Dev Center: NSTableViewDelegate Protocol Reference

このプロトコルは Mac OS X 10.6 から新設されたもので、そのほとんどのメソッドは従来は Informal Protocol として用意されていたもの(なので 10.5 まででも基本的に使える)。

Mac Dev Center: NSTableView Class Reference


ちょっとサンプルを組んでみよう。
(続く)

2009年11月23日月曜日

git - タグ付けの覚え書き

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

git のタグ付けを試行錯誤。ようやく感じがつかめた。今後はサンプルを作ったらコミット&タグ打ちして、そのタグのついた github の URL を掲載すれば良いことがわかった。


(1) ファイル TEST.1 を追加

$ touch TEST.1
$ git add TEST.1
$ git commit -m "add"
[master a890359] add
0 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 TEST.1
$ git push origin master
Counting objects: 3, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 233 bytes, done.
Total 2 (delta 1), reused 0 (delta 0)
To git@github.com:xcatsan/sandbox.git
97ceb55..a890359  master -> master
$ 

結果:
master: TEST.1


(2) タグ TAG.1 を追加

$ git tag TAG.1
$ git push origin TAG.1
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:xcatsan/sandbox.git
* [new tag]         TAG.1 -> TAG.1

結果:
master: TEST.1
TAG.1 : TEST.1


(3)ファイル TEST.2 を追加し、TAG.2 を追加

$ touch TEST.2
$ git add TEST.1
$ git commit -m "add"
[master 375f883] add2
0 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 TEST.2
$ git tag TAG.2
$ git push origin master
Counting objects: 3, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 225 bytes, done.
Total 2 (delta 1), reused 0 (delta 0)
To git@github.com:xcatsan/sandbox.git
a890359..375f883  master -> master
$ git push origin TAG.2
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:xcatsan/sandbox.git
* [new tag]         TAG.2 -> TAG.2

結果:
master: TEST.1, TEST.2
TAG.1 : TEST.1
TAG.2 : TEST.1, TEST.2


(4) ファイル TEST.3 を追加し、TAG.3 を追加

ただし master は push しない

$ touch TEST.3
$ git add TEST.3
$ git commit -m "add3"
[master a6aa0b7] add3
0 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 TEST.3
$ git tag TAG.3
$ git push origin TAG.3
Counting objects: 3, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 227 bytes, done.
Total 2 (delta 1), reused 0 (delta 0)
To git@github.com:xcatsan/sandbox.git
* [new tag]         TAG.3 -> TAG.3

結果:
master: TEST.1, TEST.2
TAG.1 : TEST.1
TAG.2 : TEST.1, TEST.2
TAG.3 : TEST.1, TEST.2, TAG.3

master を push する

$ git push origin master
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:xcatsan/sandbox.git
375f883..a6aa0b7  master -> master

結果:
master: TEST.1, TEST.2, TEST.3
TAG.1 : TEST.1
TAG.2 : TEST.1, TEST.2
TAG.3 : TEST.1, TEST.2, TEST.3


(5) ファイル TEST.2 を削除し、TAG.4 を追加

$ rm TEST.2  <
$ git rm TEST.2
rm 'TEST.2'
$ git commit -m "del"
[master 52fa1fb] del
 0 files changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 TEST.2
$ git push origin master
Counting objects: 3, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 220 bytes, done.
Total 2 (delta 1), reused 0 (delta 0)
To git@github.com:xcatsan/sandbox.git
   a6aa0b7..52fa1fb  master -> master
$ git tag TAG.4
$ git push origin TAG.4
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:xcatsan/sandbox.git
* [new tag]         TAG.4 -> TAG.4

結果:
master: TEST.1, , TEST.3
TAG.1 : TEST.1
TAG.2 : TEST.1, TEST.2
TAG.3 : TEST.1, TEST.2, TEST.3
TAG.4 : TEST.1, , TEST.3


トラブル集

git rm やらずにコミット

# On branch master
# Changed but not updated:
# (use "git add/rm ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# deleted: TEST.2
#
no changes added to commit (use "git add" and/or "git commit -a")


 タグが無いのにpush
$ git push origin TAG.4
error: src refspec TAG.4 does not match any.
error: failed to push some refs to 'git@github.com:xcatsan/sandbox.git'




参考情報・その他

Pro Git - Pro Git 2.6 Git の基本 タグ

実例:
xcatsan's sandbox at master - GitHub

2009年11月22日日曜日

今週のCocoa情報(11/22) - 今週気になった Cocoaプログラミング情報の紹介

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

OpenSSL on iPhone - Stack Overflow
iPhone Dev Center: Certificate, Key, and Trust Services Programming Guide: Certificate, Key, and Trust Services Tasks for iPhone OS

iPhone で OpenSSL を使いたいがうまくいかない -> Security.framework を使ってはどうか、といった話題。iPhone Dev Center に証明書や公開鍵(秘密鍵)等のプログラミングガイドがある。


Hashes in Cocoa and Objective-C - Stack Overflow
Cocoa でハッシュ値の計算をしたい。
=> CommonCrypto が紹介されていた( "whichi is part of libsystem on MacOSX" らしい)。他には OpenSSL を使うなど。
'
CommonCrypto の使い方は下記に解説がある。
Cocoa with Love: HashValue: an object for holding MD5 and SHA hashes

ほー。CommonCryptoのことは知らなかった。

こんなのもある。
Hollow Out: Using CommonCrypto library to recover the Cisco IPSec VPN Group Password



Regular Expressions in Objective-C and Core Data - Stack Overflow
Cocoa で正規表現を扱う話題。
下記が紹介されていた。


和製のフレームワークでこんなのもある。



MOONGIFT: » Mac OSX/iPhoneアプリ開発者必携!Objective-Cクラスブラウザ「RuntimeBrowser」:オープンソースを毎日紹介



Cocoa Builderr


ずいぶん前から落ちている。HDDがおなくなりになったとのこと。残念です。

2009年11月21日土曜日

Cocoa Bindings のデバッグTips - ” is not key value coding-compliant for the key "

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

Cocoa bindings を使っているたまに遭遇するエラーとして、指定したキーパスが存在しないケースがある。下記は Homepage エンティティに image というキーが無いと指摘するエラー。

2009-11-21 12:25:07.548 BlogAssistant[7361:10b] [ valueForUndefinedKey:]: the entity Homepage is not key value coding-compliant for the key image.

もう少し詳しい情報を出力するオプションがあるらしい。

Mac Dev Center: Cocoa Bindings Programming Topics: Troubleshooting Cocoa Bindings

上記内の Binding to the incorrect key path によると詳細情報を出力するオプションがあるとのこと。起動時オプションに下記を加える。
-NSBindingDebugLogLevel 1






エラーメッセージはこんな感じ。
2009-11-21 12:38:11.667 BlogAssistant[7424:10b] Cocoa Bindings: Error accessing value for key path image of object  (entity: Homepage; id: 0x161200  ; data: {
    createdDate = 2009-11-17 06:08:46 +0900;
    imageName = "sample2.png";
    memo = MEMO;
    modifiedDate = 2009-11-17 06:08:46 +0900;
    title = "TEST TITLE:2009-11-17 06:08:46 +0900";
    url = "http://xcatsan.com/";
}) (from bound object (null)): [ valueForUndefinedKey:]: the entity Homepage is not key value coding-compliant for the key image.
対象となるモデルの情報が表示される(だけ)のようだ。

2009年11月20日金曜日

NSViewController (2) - Nibから読み込む

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: NSViewController

リファレンスによれば NSViewController は通常 nibファイルから読み込まれるとある。

An NSViewController object manages a view, typically loaded from a nib file.

Mac Dev Center: NSViewController Class Reference

前回紹介したサンプルもだいたいそんなかんじだった。前回のコードを変更して試してみよう。


nibファイルの作成

新しく空の nibファイル を作成する。

前回作ったカスタムビューをコピーする。

File's Owner のクラスを(同じく前回用意した)ViewController に設定する。

イメージとテキストフィールドの Bindings の Value を File's Owner の representedObject へバインドする。

File's Owner の接続はこんな感じ。

nib 経由の NSViewController 作成

前回アウトレット経由で取得していた ViewController へのリファレンスを、nib経由に変更する。追加したのは1行だけ(太字)。

AppController.m

@implementation AppController

- (void)awakeFromNib
{
imageFile = [[ImageFile alloc] init];
imageFile.image = [NSImage imageNamed:@"sample"];
imageFile.name = @"sample.jpg";
imageFile.date = [NSDate date];

viewController = [[NSViewController alloc]
initWithNibName:@"SampleView" bundle:nil];
[viewController setRepresentedObject:imageFile];
[view addSubview:[viewController view]];
}

@end


これだけOK。

実行してみよう。

出た。

ソースコード

githubからどうぞ
xcatsan's SampleCode at 2009ViewControllerStudy at 20091120 from xcatsan's SampleCode - GitHub1120 - GitHub


考察

イメージを描いてみた。

こうすると NSViewController の役割が(多分)よくわかる。

NSViewController は View と Model の間を取り持つ役割を果たしている。View から見るとModelの実装が隠されていて、表示に必要な情報を得るには(あるいは保存するには) NSViewController が提供する representedObject だけを見れば(bind)良い。言い換えると開発時にViewの制作者はModelの実装を知る必要ことなくコントロールを配置や設定に専念することができる。お互いに実装が隠されることで Model の変更が View へ与える影響は少なく、その逆の場合も影響は少ない。

また nib に NSViewController を入れることでコンポーネント的な扱いができるようにもなる。複数並べたり、別のプロジェクトでも使い回せるということ。利用する側は nib を読み込み reprsentedObject と Model を紐づけるだけで良い。一旦紐づければ後は Cocoa bindings が働いて、コードを書かなくとも View と Model の同期が自動的に行われる。NSCollectionView ではまさに NSCollectionViewItem(NSViewControllerのサブクラス)をこの目的で使っている。

試しに先ほどのサンプルに手を加え NSViewController を複数作成して配置してみた。これを発展させれば NSCollectionView のようなものが作れるだろう。
#ただ大量にビューを並べると様々なリソースを食い過ぎるので工夫が必要だと思われる。



複数並べた版のソースコード:
ViewControllerStudy at 20091120-2 from xcatsan's SampleCode - GitHub

2009年11月19日木曜日

NSViewController

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

NSViewController は Mac OS X 10.5 から導入されたクラスで名前の通りビューを対象としたコントローラ。テーブルビューのカスタマイズを調査する過程で気になったので少し調べてみた。

Mac Dev Center: NSViewController Class Reference

NSViewController

リファレンスから説明を転載する。




An NSViewController object manages a view, typically loaded from a nib file.
This management includes:
  • Memory management of top-level objects similar to that of the NSWindowController class, taking the same care to prevent reference cycles when controls are bound to the nib file's owner that NSWindowController began taking in Mac OS v 10.4.
  • Declaring a generic representedObject property, to make it easy to establish bindings in the nib to an object that isn't yet known at nib-loading time or readily available to the code that's doing the nib loading.
  • Implementing the key-value binding NSEditor informal protocol, so that applications usingNSViewController can easily make bound controls in the views commit or discard the changes the user is making.

ポイントは3つある。一つ目は NSWindowController 同様にメモリ管理する役割。ただ見るところ管理というほど大げさではなく、実際はモデルとビューのリファレンスを持っているということか。

2つ目はそのリファレンスの一つでモデルへの参照 representedObject を持っているということ。これは後ほど紹介するが Interface Builder で Cocoa bindings を使う時のパスの一部として使える。

3つ目は非形式プロトコル NSEditor を実装していること。

使った事が無いので推測になるが、変更点の確定や破棄(アンドゥ)が行えるようだ。


NSViewController が無くてもアプリは作れるが、MVC で構成する際にこれまでの経験からよく使われるだろう機能をコントローラーとしてまとめたクラスだといえる。


ADC提供のサンプル

NSViewController を使ったサンプルが ADCからいくつか提供されている。

プルダウンから NSViewController を切り替えるサンプル。それぞれのタイプ(Image, Table, Video, iSight Camera)毎に NSViewController およびそれを含む Nibファイルが用意されている。


メインは NSCollectionView。


自作サンプル

NSViewContorller をつかったサンプルプログラムを試しに作ってみる。

まずは Cocoa Application のプロジェクトを作り、毎度おなじみの AppController クラスを用意しておく。

AppController.h

@class ImageFile;
@interface AppController : NSObject {

IBOutlet NSView* view;
IBOutlet NSViewController* viewController;

ImageFile* imageFile;
}

@end


ImageFile は表示内容を保持するモデルクラス。

ImageFile.h

@interface ImageFile : NSObject {

NSImage* image;
NSString* name;
NSDate* date;
}
@property (retain, nonatomic) NSImage* image;
@property (retain, nonatomic) NSString* name;
@property (retain, nonatomic) NSDate* date;

@end


次にInterface Builder を立ち上げる。メインウィンドウには Custom View を一つ貼付けておく。

それとは別に Custom View を一つ用意する。ここへ NSImageView と2つのラベルを貼付けておく。


さらに View Controller と AppController もインスタンス化し、必要なアウトレットを接続しておく。
構成はこんな感じになる。

View Controller の接続状況。

Custome View の画像とラベルの Bindings を設定する。Bind先を View Controller として、Model Key Path に representedObject.[ImageFileのプロパティ名]としておく。これだけで View Controller を介してモデルの内容が表示される。

NSViewController の representedObject とモデル(ImageFile)との紐付けおよび、メインウィンドウへのビューの追加は AppController で行う。

AppController.m

#import "AppController.h"
#import "ImageFile.h"

@implementation AppController

- (void)awakeFromNib
{
imageFile = [[ImageFile alloc] init];
imageFile.image = [NSImage imageNamed:@"sample"];
imageFile.name = @"sample.jpg";
imageFile.date = [NSDate date];

[viewController setRepresentedObject:imageFile];
[view addSubview:[viewController view]];
}

@end

テスト用にモデルのインスタンスを一つ用意した。画像はあらかじめ Resources フォルダへコピーしてある。


さて動かしてみよう。

出た。
...検証目的なのでこれといって役に立つものではない。


ソースコード

GitHubよりどうぞ。
ViewControllerStudy at 20091119 from xcatsan's SampleCode - GitHub


NSCollectionView

NSCollectionView も NSViewController を使った例の一つといえる。NSCollectionView で使う NSCollectionViewItem はずばり NSViewController から派生している。

Mac Dev Center: Collection View Programming Guide: Quick Start for Collection Views

Mac Dev Center: NSCollectionView Class Reference
Mac Dev Center: NSCollectionViewItem Class Reference

過去に NSCollectionView の解説をしているので興味の有る方はどうぞ。
Cocoaの日々: NSCollectionView

クラス相関図(もどき)
study01-2.png (image)

2009年11月18日水曜日

BlogAssistant(2) - NSTableViewへ画像を表示する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: BlogAssistant(1) - CoreData を NSTableView へ表示させる(NSArrayController経由でバインド)


前回は CoreData をテーブルビューへ表示する簡単なプログラムを作った。今回はテーブルビューに画像を表示させてみよう。画像データはファイルとして保存し、CoreData ではファイル名だけを管理するようにする。


サンプル画像の準備

最初はサンプル画像を用意してそれを表示するだけにする。後々は Webページのサムネイルが表示される。
今回は  sample1.png と sample2.png と2つ用意してみた。


NSImageCell 追加

まず InterfaceBuilder を開き、テーブルビューのカラムに NSImageCell を設定する。カラムの高さは NSTableView の属性(size)で調整した。

画像の表示方法

前回同様できるだけコードを書かずにすませたい。画像の表示も Bindings を使う。ただ CoreData にはファイル名しか格納されていないのでそのままでは表示できない。なんらかの方法で NSImage に変換させる必要がある。今回は Bindings の Value Transformer という機構を使い、ファイル名 => NSImage 変換を行わせよう。


バインディング

前回の設定に Value Transformer へ ImageTransformer を追加する。これはクラス名を表していてこの後定義する。

NSValueTransformer

NSValueTransformer は bindings を使った MVCアーキテクチャの中で、モデルとビュー間のデータ形式を変換するのに使う。使い方は NSValueTransformer のサブクラスを用意し、これをビュー側の bindings設定へ追加するだけ。こうするとモデルから渡されたデータは NSValueTransformer で変換された後、ビューで表示に使われる。逆にビューから入力されたデータを NSValueTransformer で変換してモデルへ格納することもできる。


(参考)

Mac Dev Center: NSValueTransformer Class Reference
Mac Dev Center: Value Transformer Programming Guide: Introduction to Value Transformers

Cocoaの日々: アプリケーションを開くを改造する(2)
Cocoaの日々: NSValueTransformer

今回は CoreData で管理しているファイル名を元に表示用の NSImage へ変換するクラスを作ってみた。モデル=>ビュー方向の変換のみ用意してある。

ImageTransformer.h
@interface ImageTransformer : NSValueTransformer {

}

@end

ImageTransformer.m

@implementation ImageTransformer

+ (Class)transformedValueClass
{
    return [NSImage class];
}

- (id)transformedValue:(id)value
{
if (!value) {
return  nil;
}
NSImage* image = nil;
NSString* path = [[NSBundle mainBundle] pathForImageResource:value];
if (path) {
image = [[NSImage alloc] initWithContentsOfFile:path];
}
return image;
}
@end


今回はサンプル画像がリソースフォルダ内にあるので、CoreDataから取り出したファイル名(value)を元に NSImage インスタンスを生成して返す。


動作確認

さて動かしてみよう。


おー出た。

こうも意図通りに動くと楽しくてしょうがない。
徐々にバインディングとCoreDataのコツが掴めてきた気がする。
Cocoa プログラミングは面白い。


ソースコード

BlogAssistant at master from xcatsan's SampleCode - GitHub

※git に慣れてなくて機能のコードを上書きしてしまった。。タグ使い方を勉強をせねば。

2009年11月17日火曜日

BlogAssistant(1) - CoreData を NSTableView へ表示させる(NSArrayController経由でバインド)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

以前、書いたようにブログ書き支援用の小アプリを作る。
Cocoaの日々: SimpleCapへ Webページのスクリーンショット機能を追加する(予定)

HTMLのAタグとサムネイル画像を作るようなアプリを考えている。名前は BlogAssistant にしよう。このデータを CoreData で管理させようと思っていろいろ試行錯誤している。今回は簡単なモデルを作成し、それを NSTableView へ表示させてみよう。

Xcodeで Core Data Application を作成する

(以下、MacOSX10.5、XCode 3.1.1で操作)

新規プロジェクトを作る。後々 Safariのプラグインに変える予定だが CoreDataの挙動を確認しながら作りたいのでまずは単体のアプリケーションとして作る。Xcodeで新規プロジェクトを作成しテンプレートの中から Core Data Application を選択する。



自動的に *_AppDelegate クラスが生成される。


このクラスにはあらかじめ Core Data 永続化スタックを使うのに必要なコードが自動的に用意されている。ヘッダファイルはこんな感じ。
BlogAssistant_AppDelegate.h

#import

@interface BlogAssistant_AppDelegate : NSObject
{
    IBOutlet NSWindow *window;
  
    NSPersistentStoreCoordinator *persistentStoreCoordinator;
    NSManagedObjectModel *managedObjectModel;
    NSManagedObjectContext *managedObjectContext;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator;
- (NSManagedObjectModel *)managedObjectModel;
- (NSManagedObjectContext *)managedObjectContext;

- (IBAction)saveAction:sender;

@end

これをベースに作って行こう。


モデルの定義

*.xcdatamodel を開いてデータモデルを定義する。エンティティ Homepage をこんな感じで作ってみた。

併せてモデルクラスを用意する。

新規ファイルを作成し、属性をプロパティ定義していく。このあたりXCodeが自動生成してくれると良いのだが(何か方法があるのだろうか?)。
Homepage.h

#import

@interface Homepage : NSManagedObject {

}
@property (retain, nonatomic) NSDate * createdDate;
@property (retain, nonatomic) NSString * imageName;
@property (retain, nonatomic) NSString * memo;
@property (retain, nonatomic) NSDate * modifiedDate;
@property (retain, nonatomic) NSString * title;
@property (retain, nonatomic) NSString * url;

@end



Homepage.m

#import "Homepage.h"

@implementation Homepage

@dynamic createdDate;
@dynamic imageName;
@dynamic memo;
@dynamic modifiedDate;
@dynamic title;
@dynamic url;

@end



@dynamic 指定している件については以前のブログを参照されたし。
Cocoaの日々: CoreData - NSManagedObject のプロパティ


テーブルビューの用意とバインディング

InterfaceBuilder を立ち上げ、ウィンドウへテーブルビューを配置する。


続いてモデルとビューの橋渡しを行う NSArrayController を MainMenu.xib へ追加する。


これを MainMenu.xib へ追加。

インスペクタを開き、Attributes を編集する。

変更点は下記の通り:
  • Mode を Entity へ変更する
  • Entity Name へ先ほど用意したモデルでのエンティティ名 Homepage を指定する
  • Prepares Content にチェックを入れる(これで NSArrayController が自動的に NSManagedObjectContext 経由で保存データを読み込んでくれる)。 

次にテーブルビューのカラムを選択した後、NSArrayController へバインドする。

Controller Key へ arrangedObjects を、Model Key Path に title(カラムに表示するモデルの属性)を指定する。

他のカラムも同様にバインドしておく。

ここまででコントローラー(NSArrayController)とビュー(NSTableColumn)とのバインドができた。後はコントローラとモデルの紐付けが必要。NSArrayController は(親クラスの NSControllerは)CoreDataをサポートしていて -[setManagedObjectContext:] がある。これを使ってコントローラとモデルを結びつける。
参照:Mac Dev Center: NSObjectController Class Reference

BlogAssistant_AppDelegate.h

@interface BlogAssistant_AppDelegate : NSObject
{
      :
    IBOutlet NSArrayController *arrayController;
}



ヘッダへアウトレットを一つ追加し、InterfaceBuilderでNSArrayController へ接続する。

最後に NSArrayController へ NSManagedObjectContext を設定する。これは今回コードで書く。

BlogAssistant_AppDelegate.m

- (void)awakeFromNib
{
[arrayController setManagedObjectContext:[self managedObjectContext]];

}



これで Model-View-Controller が全てつながった。データの管理は NSManagedObjectContext が良きに?取りはからってくれる。


テスト用ボタン追加

表示を確認するにはテストデータが必要。テストデータを追加するボタンを付ける。

ボタンのターゲットは BlogAssistant_AppDelegate とし、アクションは addTestRecord: につなぐ。実装はこんな感じ。

BlogAssistant_AppDelegate.m

- (IBAction)addTestRecord:(id)sender
{
Homepage* homepage = [NSEntityDescription insertNewObjectForEntityForName:@"Homepage"
inManagedObjectContext:[self managedObjectContext]];
homepage.title = [NSString stringWithFormat:@"TEST TITLE:%@", [[NSDate date] description]];
homepage.imageName = @"TEST IMAGE";
homepage.url = @"http://xcatsan.com/";
homepage.createdDate = [NSDate date];
homepage.modifiedDate = [NSDate date];
homepage.memo = @"MEMO";

NSError* error = nil;
[[self managedObjectContext] save:&error];

if (error) {
NSLog(@"INSERT ERROR: %@", error);
} else {
NSLog(@"INSERTED");
}

}


保存形式を SQLite へ変更する

自動生成されたコードでは保存形式が XMLになっている。これを SQLへ変更する。


BlogAssistant_AppDelegate.m

- (NSPersistentStoreCoordinator *) persistentStoreCoordinator {

    url = [NSURL fileURLWithPath: [applicationSupportFolder stringByAppendingPathComponent: @"BlogAssistant.db"]];
    persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
    if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error]){
        [[NSApplication sharedApplication] presentError:error];
    }  


}



動作確認

動かしてみよう。起動して何度かボタンを押してテストデータを生成する。


NSManagedObjectContext へ対するテストデータ追加がテーブルビューへ自動的に反映された。ダブルクリックすると変更もできる。

アプリを止めて再起動するとデータが復元される。

ここまでで MVCの連携コードを書いて InterfaceBuilder を設定しただけ。CoreDataとBindingsを使うと、ローカルDB付きのテーブルビューがこんなに簡単にできてしまう。


保存データ

さてデータはいったいどこに保存されているのか。自動生成されたコードを見るとライブラリ配下の Application Support にあるようだ。

    url = [NSURL fileURLWithPath: [applicationSupportFolder stringByAppendingPathComponent: @"BlogAssistant.db"]];



ターミナルを開き sqlite3 コマンドで中身が確認できる。




ソースコード

github へ上げました。
BlogAssistant at master from xcatsan's SampleCode - GitHub



おまけ:SQLite のログ

実行時オプションにを -com.apple.CoreData.SQLDebug 1 付けるとデバッガコンソールへ SQLite のログを出力させることができる。

(参考)Cocoa Touch の日々: CoreData で発行されている SQL をデバッグ出力する

こんな感じ。


- - - -
BlogAssistant開発:続く..