Cocoaバインディングの値の変換機能
(NSValueTransformer)を試してみる


Cocoaバインディングには値変換という機能があります。これはモデルを表現したオブジェクトのデータ表現の形式(簡単にいえばデータの型ですね)とビュー上の表現形式を相互に変換するというものです。

例えば簡単な例では、顧客情報を表わすモデルオブジェクトに、その顧客のメールアドレスに新製品情報等を送ってよいかを表わすフラグがあったとします。
この例のモデルデータのオブジェクトでは、この項目はBOOL値(1 or 0)を持つNSNumberとして実現するのが一番効率的です。しかし、画面(ビュー)に表示するときには1や0といった数値ではなく、「はい」または「いいえ」といった文字列で表現しないと操作する人間にはわかりません。

そこでこのような場合にコントローラレイヤが表現の相互変換を行えるようにするのが値変換機能です。

値変換機能の主役はNSValueTransformerクラス(AppKitではなくFoundationなので要注意)です。
実際はこのクラスそのもののインスタンスを使用するわけではなく、このクラスのサブクラスを作成し、クラスで定義されている変換メソッド内で自分が必要とするオブジェクトの変換処理をインプリメントすることになります。

値変換機能の詳細についてはmkinoさんのサイトHMDTにあるAppleドキュメントの和訳記事を参照してください。

http://homepage.mac.com/mkino2/panther/ValueTransformers/ValueTransformers.html

2004/06/14追記:このページではビュー/モデル間の変換についてのみ限定してかいていますが、Bindingの設定でValueTransformerが設定できる場所であればビュー/ビュー間等でも変換可能と思われます。

1.組み込む値変換機能について

今までいじってきたCocoaバインディングサンプルに値変換機能を組み込みますが、あまり大きくいじり回す元気がないので(^_^;)

 ・Master Interface部にあるリストの表示文字色をコメント欄に何か値が入っていれば青色で表示する

と、少々しょぼい機能で我慢してください(下図参照)。m(__)m

また、今回は表示を変えるだけなのでビュー→モデルデータ方向の変換は組み込みませんので、それについては各自実験を行ってみてください。

値変換機能の組み込みは以下のステップからなります。

(1) 自分の目的にあったNSValueTransformerのサブクラスを作成し、決められた必須メソッドおよび値の変換メソッドを実装する
(2) File's Ownerのdelegateとして設定したクラスのアプリケーション起動通知メソッドに、Cocoaバインディングメカニズムが内部に持っている値変換クラスのリストに(1)で作成したクラスに”名前をつけて”を登録する処理を追加する
(3) Interface Builderでビュー上の値変換を行いたいカ所のBindingの設定で値変換処理として(2)で登録したクラスの”名前”を指定する

この中で特に引っ掛かりやすいのは(2)だと思います。私も最初引っ掛かっちゃいました。(^_^;)
今までがInterface Builder(以下IB)上の設定でほとんどできていたので、値変換の説明をよく読まないまま、変換クラスも作ったし、((2)のステップを知らずに)IB上でBindingの設定も行ったし、「これでよし!」と走らせてみたら動きませんでした。(;_;)
私個人的には、なんだかここだけ他のCocoaバインディング関連の操作から浮いているような感じがしてます。
ですので皆さんもご注意くださいね。

2.自前の値変換クラス(UCColorTransformer)の作成

2.1 クラスのソースファイルの作成

今まで、例えばアプリケーションクラスインスタンスのdelegateとなるような自前のコントローラのクラスを作成する場合、IB上でNSObjectのサブクラスを作成するという操作で作りますが、NSValueTransformerのサブクラスはそれができません。(そもそもNSValueTransformerがnibパネルのClassesペインに出てこない!)

というわけでXcode上で新規ファイルの作成メニューで「Objecttive-C class」としてUCClolorTransFormerというクラスのソースファイルを作成します。


2.2 値変換クラスのインプリメント

作成した変換クラスのソースのひな形に対して以下のコーディングを行う必要があります。

(1) 親クラスがNSObjectになっているのでNSValueTransformerに変更する
(2) NSValueTransformerが要求する実装必須のクラスメソッドを実装する
(3) モデルオブジェクト値→ビュー上の表示値変換メソッドを実装する
(4) 必要な場合はビュー上の表示値→モデルオブジェクト値変換メソッドを実装する

(2)のNSValueTransformerが要求する実装必須のクラスメソッドというのは以下のメソッドです。

// 変換後の値がどのクラスのオブジェクトなのかを通知する
+ (Class)transformedValueClass;

// ビュー→モデルオブジェクトへの変換を許すかを通知する
+ (BOOL)allowsReverseTransformation;

(3), (4)の変換メソッドの仕様は以下のように定義されています。

// モデル→ビューへの変換
- (id)transformedValue:(id)value

// ビュー→モデルへの変換
- (id)reverseTransformedValue:(id)value

モデル→ビュー変換メソッド「transformedValue:」の場合、パラメータのvalueがモデルオブジェクト内の値、メソッドの復帰値が変換後の値で、「+ (Class)transformedValueClass;」で通知したクラスのオブジェクトである必要があります。
同様に ビュー→モデルへの変換「- (id)reverseTransformedValue:(id)value」はその逆となります。

インプリメントした変換クラスのコードを以下に示します。

UCColorTransFormer.h:リスト表示色変換クラスインターフェースファイル
@interface UCColorTransFormer : NSValueTransformer
{
}

//
// 実装必須なクラスメソッド
//
// 変換後の値のクラス
+ (Class)transformedValueClass;

// 逆変換の有無
+ (BOOL)allowsReverseTransformation;

//
// 値変換メソッド
//
// モデル→ビュー変換メソッド(コメント有無による表示色制御)
- (id)transformedValue:(id)value;

@end

UCColorTransFormer.m:リスト表示色変換クラスインプリメントファイル
@implementation UCColorTransFormer

//
// 実装必須なクラスメソッド
//
// 変換後の値のクラス
+ (Class)transformedValueClass
{
    // 表示色の制御なのでNSColorのクラスを返す
    return [NSColor class];
}

// 逆変換の有無
+ (BOOL)allowsReverseTransformation
{
    // ビュー→モデル変換は行わない
    return NO;
}

//
// 値変換メソッド
//
// モデル→ビュー変換メソッド(コメント有無による表示色制御)
- (id)transformedValue:(id)value
{
    if ( [(NSString *)value length] > 0 ) {
        // コメントがあれば青色
        return [NSColor blueColor];
    } else {
        // コメントがなければ黒
        return [NSColor blackColor];
    }
}

@end


3.アプリケーション起動時に自前値変換クラスのインスタンスを登録する

File's OwnerのdelegateとしてリンクしてあるUCAppControllerクラスの、アプリケーション起動時に呼び出されるメソッド「applicationDidFinishLaunching:」の先頭に以下の処理を追加します。

    // Cocoaバインディングの値変換オブジェクト登録
    UCColorTransFormer *poTrans = [[[UCColorTransFormer alloc] init] autorelease];
    [NSValueTransformer setValueTransformer:poTrans
                                    forName:@"UCColorTransFormer"];

やっている内容は、自前の変換クラスのインスタンスを生成し、それをNSValueTransformerのクラスメソッド

// 指定された名前の値変換オブジェクトの登録
+ (void)setValueTransformer:(NSValueTransformer *)transformer forName:(NSString *)name

でNSValueTransformerのクラスが内部に持っていると思われる変換クラスインスタンスのリストに名前を付けて登録することです。
なお、ここで指定する名前はビューのBinding設定で使用しますので他と重ならないよう注意してください。

4.ビューとのバインディング

最後にビューと値変換クラスとのバインディングです。
NSTableViewの表示のバインディングを行うので、値のバインディング設定時と同様にテーブル内のカラムを選択状態にしてください。

そしてinfoパネルを開き、その中の「TextColor」の項目を開きます。

ここで以下の設定を行います。

「Bind」ボタンにチェックを入れてこのボタンがコントローラとバインドされることを示します。(明にチェックしなくても「Binding to:」の設定を行う事で自動的にチェックされます)
「Binding to:」に「BookController」を選択します。
「Controller Key:」に表示する値の時と同様「arrangedObjects」を選択します。
「Model Key Path:」にはコメントの有無で表示色を制御させるので「comment」を選択します。
「Value Transformer」に「UCColorTransFormer」と入力します。選択でないことに注意!!(選択肢の中に出てきません)
ここで指定するのはアプリケーション起動時に登録した値変換オブジェクトに付与した名前です。

作者のカラムに付いても同様に設定を行います。

4.いよいよ動かしてみる

以上で作業は完了ですので、ビルドして動かしてみましょう。
無事動きましたでしょうか。

【余談】

このサンプルでは、NSValueTransformerクラスへの自前値変換クラスインスタンスの追加をapplicationDidFinishLaunching:で行っているわけですが、最初に組み込んでビルドして動かしたとき、値変換処理へのバインディングが正しく認識されないようで、変換処理の失敗でうまく動きませんでした。

実行ログに出てくるエラーメッセージの内容からの推測や、デバッガで変換処理にブレークポイントを設定しても止まらなかったことから、登録のタイミングが遅いからなのだろうと思い、applicationWillFinishLaunching:に変えてためしてみるとうまくいき、そういうものなのだろうと納得してその日はそこで作業を終わりました。

翌日、このページに載せるために、うまく動かなかったときのログなどの情報を採取するするべく、アプリケーション起動通知のメソッドをapplicationDidFinishLaunching:に戻して動かしてみると、なんと正しく動作するではないですか!! 「(松田優作風に)なんじゃこりゃ〜」な気分です。

前にも別件で、ソース上は正しいのにうまく動かないことがあって、ターゲットのクリーンとリビルドなどを何度か行っているうちになぜか動くようになったことがあって、推測するに、どうも今のXcodeのゼロリンク機能はいまいちタコなところがあるように感じています。
でも本来正しい方式でコーディングしていたのに、このバグ(?)のため誤った方向にソースを修正してしまったらジャレになりませんよね。Appleさん、もっとがんばって品質をあげてください。

サンプルプログラムのダウンロード(1.5MB)

【作成・確認環境】
Mac OS X v10.3.4
Xcode v1.2

一覧に戻る