CocoaBindingの使い方サンプル

CocoaBindingはInterface Builderから簡単に利用できるようになっているが、正直その裏の仕組みは、ちゃんと理解できていない...。しかし、仕組みが100%理解できていなくても、ケース別の書き方が理解できれば、便利に使えてしまうところが素晴らしい。詳細な仕組みを理解するのは後回しにして、自分なりに理解できた利用パターンをメモ。

仕様

  • サンプルアプリケーションの仕様は、前回と同じく以下のような単純なものだ。(プロジェクト名は「SliderControl」にした。)
    • Sliderを動かすと、連動してTextFieldの数値が変化する。
    • TextFieldに数値を入力すれば、Sliderが数値に対応した位置に移動する。

共通の作業


  • スライダーのContinuousはチェックありの状態(スライダーを選択して「コマンド + 1」でAttributesインスペクタを開く。)


モデルに直結したBinding

  • Xcodeでスライダーとテキストフィールドの値を保持するBindModelクラスを新規作成する。
// ---------- BindModel.h ----------
#import <Cocoa/Cocoa.h>


@interface BindModel : NSObject {
    int alpha;
}

@end
// ---------- BindModel.m ----------
#import "BindModel.h"


@implementation BindModel

@end
  • Interface BuilderのMainMenu.nibウィンドウに、BindModelクラスのインスタンスを追加する。
    • ライブラリのObjectをドラッグして、MainMenu.nibウィンドウに追加する。

    • 追加したObjectのIdentityインスペクタを開き(コマンド + 6)、Class項目で「BindModel」を選択して設定する。


  • スライダー、テキストフィールドのバインディングを以下のように設定する。
    • Controller Keyの設定は不要。
    • Model Key Pathに「alpha」を設定したのみ。

以上で作業完了。以下のようなイメージでビューとモデルが直結したバインディング状態だ。

ビュー
 ↓  ↑  
モデル  BindModel

ObjectControllerでさらにシンプルなBinding

実際には上記のような単純な接続の場合、わざわざ自分でモデルを設定するまでもない。既存のObjectControllerを利用すれば、あとはObjectControllerの方でモデルを準備してくれる。

  • Interface BuilderのMainMenu.nibウィンドウに、ライブラリからObjectControllerをドラッグして追加する。

  • スライダー、テキストフィールドのバインディングと、ObjectControllerのAttributesを以下のように設定する。
    • スライダーとテキストフィールドのModel Key Pathに「alpha」を設定。
    • ObjectControllerのPrepares Contentにチェックマークを入れる。(ここがチェックありの状態の時、ObjectControllerは必要に応じてモデルを準備してくれる。)
    • ObjectControllerのClass Nameは、必要に応じて準備するモデルのクラス名になる。デフォルトではNSMutableDictionaryになっている。

以上で作業完了。コーディングは一切ない。接続のイメージは以下のような状態だ。

ビュー
 ↓  ↑  
コントローラー  NSObjectController
 ↓  ↑  
モデル  NSMutableDictionary

UserDefaultControllerで設定状態を保持するBinding

実はBindingのデフォルトの接続先(Bind to:)はUserDefaultControllerになっている。
ユーザーインターフェース設定後、コントローラーもモデルも追加せずにModel Key Pathに「alpha」と設定すると、MainMenu.nibウィンドウに自動的にUserDefaultControllerが追加されるのだ!

以上で作業完了。たった二つの設定をしただけで、スライダーとテキストフィールドの状態が保持されることになってしまった。試しに一旦サンプルアプリケーションを終了して、もう一度起動してみると、スライダーとテキストフィールドは以前の状態で表示された。スライダーとテキストフィールドの値「alpha」は、環境設定ファイルとして以下のパスに保存されている。(このプロジェクト名は「SliderControl」)

~/Library/Preferences/com.yourcompany.SliderControl.plist

接続のイメージは以下のような状態だと思う。

ビュー
 ↓  ↑  
コントローラー  NSUserDefaultsController
 ↓  ↑  
モデル  NSUserDefaults
 ↓  ↑  
ファイル  ~/Library/Preferences/com.yourcompany.SliderControl.plist

最もシンプルな設定で、オブジェクトの状態まで保存してしまう仕組みに感動!

色(NSColor)の設定状態を保持するために


色(NSColor)をファイルに保存しようとする場合、かつては煩雑な手順を踏んでいた。それはNSColorというクラスが自身をファイルに変換する方法をサポートしていないのが原因だ。だから伝統的なCocoaのコーディング手法では以下のようにしていた。NSArchiverとNSUnarchiverを利用して、オブジェクトをバイナリデータに変換、再変換している。

// ファイルに保存する場合
NSData *theData = [NSArchiver archivedDataWithRootObject:aColor];
[[NSUserDefaults standardUserDefaults] setObject:theData forKey:aKey];
// 上記ファイルから読み取る場合
NSColor *aColor = nil;
NSData *theData = [[NSUserDefaults standardUserDefaults] dataForKey:aKey];
if (theData != nil)
    aColor = (NSColor *)[NSUnarchiver unarchiveObjectWithData:theData];

NSColorがファイルに変換する方法をサポートしてくれたらこんな手間は不要になるのに...と言いたいところだが、CocoaBindingには「Value Transformer」という項目がある。ここで、「NSUnarchiveFromData」を選択するだけでOK。これだけでNSColorもちゃんとファイルに保存されるようになる。素晴らしい!

接続のイメージは以下のような状態だと思う。

ビュー
 ↓  ↑  
コントローラー  NSUserDefaultsController
 ↓  ↑  
Value Transformer  NSUnarchiveFromData
 ↓  ↑  
モデル  NSUserDefaults
 ↓  ↑  
ファイル  ~/Library/Preferences/com.yourcompany.SliderControl.plist
  • ちなみに、もしValue Transformerを設定し忘れると、アプリケーションはクラッシュする。Xcodeから実行した場合はデバッガが起動する。
  • Value Transformerには、以下の変換方法が用意されている。(もっと詳しく>> 使用可能な値変換
    • NSString * const NSNegateBooleanTransformerName;// YESをNOに、NOをYESに変換
    • NSString * const NSIsNilTransformerName;// nilならYESに変換
    • NSString * const NSIsNotNilTransformerName;// nilでなければYESに変換
    • NSString * const NSUnarchiveFromDataTransformerName;// オブジェクトとバイナリデータの変換
    • NSString * const NSKeyedUnarchiveFromDataTransformerName;// 今イチ使い方がよく分からない。どんな状況で使うのだろう?
参考

MacBookXcodeがインストールされている場合、以下のドキュメントがとても参考になる。

  • Color Programming Topics for Cocoa - Storing NSColor in User Defaults
    • file:///Developer/Documentation/DocSets/com.apple.ADC_Reference_Library.CoreReference.docset/Contents/Resources/Documents/documentation/Cocoa/Conceptual/DrawColor/index.html:title
  • NSValueTransformer Class Reference
    • file:///Developer/Documentation/DocSets/com.apple.ADC_Reference_Library.CoreReference.docset/Contents/Resources/Documents/documentation/Cocoa/Reference/Foundation/Classes/NSValueTransformer_Class/index.html

ObjectControllerの管理モデルに、自作モデル(例:BindModel)を設定するBinding


今までObjectControllerを利用する時にはモデルのことはすべて任せきりだったが、自作モデルの管理を任せることも出来る。モデルお任せの時とBindingインスペクタの設定は同じ。以下の設定をすることで自作モデルを管理してくれるようになった。

  • ObjectControllerのAttributesインスペクタウィンドウで、Prepares Contentのチェックマークを外す。(チェック無しの状態)

  • 自作モデル(例:BindModel)をMainMenu.nibウィンドウに追加しておく。

  • ObjectControllerのアウトレット(content)を、自作モデル(BindModel)と接続する。(ObjectControllerからBindModelに向けてコントロールキー + マウスドラッグ)

以上で作業完了。接続のイメージは以下のような状態だ。

ビュー
 ↓  ↑  
コントローラー  NSObjectController
 ↓  ↑  
モデル  BindModel

BindModelの「alpha」はint型にしてある。スライダーを動かすとテキストフィールドには整数値が表示される。そのことでObjectController経由でBindModelが活用されていることが分かる。