通貨換算機を作ってCocoaBindingの理解を深める

以下のページを参考に「通貨換算機(Currency Converter)」を作ってみる。このサンプルコードは素晴らしい!CocoaBindingの理解が自然と深まった。日本語訳に深く感謝!

      • 素晴らしいサンプルであるのに、本家Apple更新履歴を確認すると、どうもずいぶん前に削除されてしまったようだ。非常ーに残念。HMDTさんのページも移転に伴い、日本語訳が残されているのは旧サイトのようだ。どうかいつまでも、上記ページが見られますように!

ということで、実際に「通貨換算機(Currency Converter)」を作って試してみた時のメモ。詳細な解説については上記ページがあるので、ここでは自分が気付いた要点のみ。

表計算ソフトで自動再計算するようにBindingする方法

  • ユーザーインターフェース
    • Exchange Rate(換算レート)とDollars to Convert(ドル)を入力すると、Amount in Other Currency(換算結果)を計算して表示する。

  • Xcodeで、Converterクラスを追加して、以下のように設定する。
#import <Cocoa/Cocoa.h>


@interface Converter : NSObject {
    double exchangeRate;
    double dollarsToConvert;
}

- (double)amountInOtherCurrency;

@end
#import "Converter.h"


@implementation Converter

// dollarsToConvertまたはexchangeRateが変化したら、変更通知amountInOtherCurrencyを発生するように設定
// その結果「Model Key Path」が「amountInOtherCurrency」となっているオブジェクトすべてが再計算され更新される
+ (void)initialize {
    [self setKeys:[NSArray arrayWithObjects:@"dollarsToConvert", @"exchangeRate", nil]
          triggerChangeNotificationsForDependentKey:@"amountInOtherCurrency"];
}

// 計算結果を返す(ドル×換算レート)
- (double)amountInOtherCurrency {
    return (double)(dollarsToConvert * exchangeRate);
}

@end
  • Interface Builderで、MainMenu.nibウィンドウにConverterとObjectControllerのインスタンスを追加する。

  • テキストフィールドのBindings
    • Model Key Pathを入力すると...Bind to:はObjectController、Controller Keyはselectionが最初から設定された状態になった。

  • ObjectControllerのAttributesとConnections
    • ObjectControllerのアウトレットcontentを、MainMenu.nibウィンドウのConverterと接続した。

以上で作業完了。

理解したこと
  • 表計算ソフトのように、あるセルの変化が、関係するセルの計算式を自動再計算するようなBindingが可能になる。
    • 「setKeys:キー配列 triggerChangeNotificationsForDependentKey:変更通知」メソッドによって、キー配列で指定したキーに変化があった時、変更通知が発生する。(この例ではamountInOtherCurrencyメソッドが呼び出される。)
    • setKeys:triggerChangeNotificationsForDependentKey:メソッドは「+ (void)initialize」クラスメソッドの中で設定すること。

NSArrayController経由でBindingして、テーブルビューをデータベースのように取り扱う方法

(Bindingの設定を変える時は、一旦Bind to:左側のチェックを外してから設定した方が、以前の設定が確実にクリアされて無駄に悩まなくて済むようだ)

      • テーブル表示にして、通貨換算の履歴が残るようにする。
      • 従来のテキストフィールドによる編集、またはテーブルの直接編集のどちらでも入力可能。
  • Converterクラスの変更は無し。
  • Interface Builderで、MainMenu.nibウィンドウにArrayControllerのインスタンスを追加する。(ConverterとObjectControllerのインスタンスは不要になるが、そのまま残しておいた。)

  • テキストフィールドのBindings
    • Bind to:をArray Controllerに変更。

  • ArrayControllerのAttributesとConnections
    • Class NameをConverterに変更。
    • Outletsのcontentは接続無しの状態。

  • TableColumnのBindings
    • まずはTabelColumnを選択する。(以下Exchange Rate列を選択する例。Exchange Rate列の真ん中辺りを、ゆっくり3回クリックすると選択状態になる。)

    • それぞれのTabelColumnを以下のように設定する。
      • Controller KeyがarrangedObjectsになっていることに注目。このように設定することでテーブルビューを配列*1と対応付けて管理できるようになる。

以上で作業完了。

理解したこと
  • NSArrayControllerを利用すれば、テーブルビューをものすごく簡単に取り扱うことができる。自作モデルとTable ColumnをBindする場合...
    • Table ColumnのBindingsインスペクタで、Controller KeyはArrangedObjectsを選択する。
    • NSArrayControllerのAttributesインスペクタで、Class Nameに自作モデルのクラス名を設定、Prepares Contentsのチェックは無しの状態にした。
    • NSArrayControllerのConnectionsインスペクタで、Outletsのcontentは接続無しの状態にした。

捕捉: NSObjectControllerが管理するモデルクラスの指定方法二つの違い(自分勝手な理解)

  • アウトレットのcontentを設定する方法
    • MainMenu.nibウィンドウ中のアイコンは、クラスからインタンス化されたオブジェクトを表現している。
    • NSObjectControllerのアウトレットであるcontentに、インタンス化されたConverterを接続すると、管理するモデルはその時点でインタンス化されたConverterに固定される。
    • AttributesのClass Nameや、Prepares Contentsのチェックの状態とは無関係に、上記で設定されたインタンス化されたConverterに固定される。

  • クラス名を設定する方法
    • NSObjectControllerのアウトレットであるcontentに、何も接続されていない場合は...
    • AttributesのClass Nameのクラスが、
    • プログラム実行中に必要に応じて自動的にインスタンス化され利用されることになる。(Prepares Contentsのチェックありの状態)


捕捉: NSArrayControllerのアウトレットcontentを、インタンス化されたConverterに接続することの意味

アプリケーション起動直後のテーブルビューの状態
Prepares Contentsのチェック無し アウトレットcontentの接続無し テーブルビューの中は0行。行が存在しない状態。
Prepares Contentsのチェックあり アウトレットcontentの接続無し 1行目としてClass Nameで指定されたクラスがプログラム実行中に自動的にインタンス化されて表示される。
Prepares Contentsのチェック無し アウトレットcontentの接続あり 1行目としてMainMenu.nibウィンドウでインタンス化されたConverterが表示される。
Prepares Contentsのチェックあり アウトレットcontentの接続あり 1行目としてMainMenu.nibウィンドウでインタンス化されたConverterが表示される。
      • プログラム実行中に「Add」ボタンで追加した行は、常にClass Nameで指定されたクラスがインスタンス化され表示される。

つまり、アウトレットcontentを接続した場合は、1行目と2行目のインスタンスのクラスが相違してしまう可能性がある。(どちらも同じクラスなら特に問題はないのだが)プログラムを書く人として、何か特別の理由が無い限り、1行目も2行目も同じ基準でインスタンス化されないと、何だか落ち着かない。だから、NSArrayControllerを利用する時は、特別な事情が無い限り、アウトレットcontentは接続無しの状態にしておくのが良いのかなと感じた。

*1:この例では、テーブルビューの一行一行がConverterクラスのインスタンスであり、その集合を配列として管理している。