« Cocoaはじめの一歩+α 練習問題5と6 | メイン | applescriptガイド »

Cocoaはじめの一歩+α 練習問題7〜12

Cocoaはじめの一歩というサイトの歩き方です。
Cocoaはじめの一歩にある練習問題を通じでXcodeとobjective-Cになじもうとする企画です。

今回は残りの問題をすべてやってしまいましょう。
練習問題7はイメージファイルをIBで扱う方法
練習問題8はインスタンス変数の使用方法
練習問題8は環境設定ファイルの使用方法
練習問題10はローカライズ
練習問題11はアイコン作成
練習問題12はデバッグ方法

ということですが、xillionオリジナルアプリ1919361でほとんどの項目が経験済みですね。。
ということで、課題を以下の2つに絞ります。

1.バインディングによるコントロール
2.環境設定ファイルの使用

えっ問題がかわってるって?気にしないで自分のやりたいことをやりましょう。
もうそんな年頃ですから。

バインディングはADCのCocoaセミナービデオで覚えましょう。
http://developer.apple.com/jp/documentation/japanese.html

今回のアプリに取り込むバインディングは以下のとおり

身長インスタンス変数 ←→ 身長テキストフィールド 
身長インスタンス変数 ←→ 身長スライダ 
このチャート(?)は、身長インスタンス変数を介して、テキストフィールドとスライダの同期がとられることを意味します。
ユーザーが身長テキストの入力すると、身長インスタンス変数を変化させ、それに伴って身長スライダも同調します。ユーザーがスライダを動かした場合は、身長テキストフィールドがシンクロします。

体重インスタンス変数 ←→ 体重テキストフィールド 
体重インスタンス変数 ←→ 体重スライダ 
体重についても同様で、テキストとスライダが同期をとって変化します。

身長インスタンス変数・体重インスタンス変数 → BMI計算
BMIインスタンス変数  → BMIテキストフィールド 
BMIインスタンス変数  → BMIインジケータ 
BMIインジケータ → 体重計算
この矢印はちょこっとフクザツです。中心となるのはBMIインスタンス変数。身長か体重が変化したときに再計算します。そのBMIインスタンス変数を介して、BMI用のテキストとインジケータが変化します。インジケータの値を変更した場合は、体重を変化させます。因にBMIテキストフィールドは表示のみでユーザーが入力できないフィールドです。詳しくは解答で。

環境設定はuserdefaultsオブジェクトを使用します。
練習問題8のとおり、前回修了時の値を覚えておいて、次回の初期値として表示させる様にしましょう

インタフェイスはこんな感じ。
スペースがあまったのでBMIの説明をいれちゃいました。


Exercise7.zip

解答です。IB部分。




IBでの作業はこんな感じです。
・ウィンドウにローラーを配置します。
 スライダやレベルインジケータは上限、加減、警告レベルや見た目の設定を行います。
・コントローラークラスを作成しインスタンス化します(MAbmiController)
・バインドキーの管理用にNSObjectControllerをインスタンス化します(バインドの詳細は下記)
・MAbmiControllerをアプリケーションのデリゲートとします。

バインディング設定の詳細です。
まず、コントロールパレットからMainMenu.nibのインスタンスにNSObjectControllerをドラッグ&ドロップします。
NSObjectControllerのインスタンスが作成されるので、インスペクタのattributesを表示させます。
ここにバインドで使用する以下のキーを追加していきます。
height(身長テキストフィールドと身長スライダにバインドします。heightはMAbmiControllerのインスタンス変数として実装します。)
weight(体重テキストフィールドと体重スライダにバインドします。weightはMAbmiControllerのインスタンス変数として実装します。)
bmi(BMIレベルインジケータにバインドします。bmiはMAbmiControllerのインスタンス変数として実装します。)
calculateBmi(BMIテキストフィールドにバインドします。calculateBmiはMAbmiControllerのメソッドとして実装します。)
calculateWeight(計算体重テキストフィールドにバインドします。calculateWeightはMAbmiControllerのメソッドとして実装します。ただしcalculateWeightは値を返さないメソッドのため計算体重テキストフィールドには何も表示されません。)

コントロールとのバインド方法は以下の通り。
コントロールのインスペクタを開き、bindのページを表示させます。(コントロールを選択して⌘+4キー)
valueの三角をクリックして詳細を表示させます。
Bind toのセレクタで、今作成したNSObjectControllerを選択します。
Cotroller Keyはselection、Model Key Pathに、先ほど追加したキーから適当なものを選びます。

最後にcontrollerインスタンスとNSObjectControllerを接続します。
接続先はNSObjectControllerのデフォルトのoutletにある"content"です。

バインドで表示の制御を行うものについてはcontrollerのoutletは不要です。
アプリからキーへのセットを行うことで、その値が表示されます。

ところが今回のアプリではちょっと厄介なことが発生してしまいました。
1.体重スライダを調節(=値を変える) 
→2.この値をMAbimControllerのインスタンス変数weightにバインド 
→3.変更をMAbimControllerのメソッドcalculateBMIに通知(=実行) 
→4.calculateBMIでBMIを変更(setValue:forKey:)しBMIテキストフィールドとBMIレベルインジケータにバインド(=表示)
とここまでは良いのですが、
BMIを変更(setValue:forKey:)するとメソッドcalculateWeightに通知(=実行)されます。するとcalculateWeightがweightを変更(setValue:forKey:)し無限ループになってしまいます。
で4.のBMIの変更(setValue:forKey:)にかえて、BMIインジケータの値をセットすることにします。
このため、NSObjectControllerにBMIレベルインジケータのアウトレットを準備します。

解答です。ソースコード(バインド)部分です。


MAbmiController.hです。
OutletはbmiLevelIndicatorのみ。その他のコントロールはバインドします。
インスタンス変数を公開します。
バインドキーとなるインスタンス変数は、ヘッダーに記述しなければなりません。
バインドするメソッドはヘッダーになくても大丈夫でした。


@interface MAbmiController : NSObject
{
  IBOutlet NSLevelIndicator *bmiLevelIndicator;
  float height;
  float weight;    
  float bmi;
}
@end

次はMAbmiController.mです。


+ (void)initialize {  
    [self setKeys:[NSArray arrayWithObjects:@"height", @"weight", nil]
      triggerChangeNotificationsForDependentKey:@"calculateBmi"];  
  [self setKeys:[NSArray arrayWithObjects:@"bmi", nil]
      triggerChangeNotificationsForDependentKey:@"calculateWeight"];  
}

クラスメソッッドの初期化でバインドキーの宣言をします。モノの本には依存キーの登録として書かれています。setKeys:triggerChangeNotificationsForDependentKey:メソッドを使用して、クラス内のキーの依存関係(バインド関係)を設定します。setKeys:に列挙されたプロパティが変更されると、triggerChangeNotificationsForDependentKey:に指定されたプロパティに変更通知が送られます。ということですが、通知はオブジェクトに送信されるのであってプロパティに送信するというのはヘンな気がしませんか。そもそもこの依存キーの登録はself=自分のクラスに対して行っているし、、、ちょっと意味が分かりませんね。たぶんオブジェクト指向のメッセージングとは違うのでしょう。。。とりあえず、今回の例では、heightかweightが変わると、calculateBMIを実行するように設定しています。
triggerChangeNotificationsForDependentKey:にインスタンスメソッドを書いておくと、setKeys:で指定したプロパティが変化したときに、そのインスタンスメソッドを実行するようです。
同様に、bmiが変わったときはcalculateWeightを実行して体重を再計算するように設定します。



-(void)calculateWeight {
  [self setValue:[NSNumber numberWithFloat:bmi * height * height / 10000] forKey:@"weight"];
}

-(float)calculateBmi {
  if (height ==0) return  0 ;
  bmi =weight / (height / 100) / (height / 100);  
  [bmiLevelIndicator setFloatValue:bmi];
  return bmi;
}


キーの依存関係から実施されるコードです。
calculateWeightでは、setValue:forKey:メソッドを使用してキー値の変更を行います。
calculateBmiでは無限ループをさけるため、普通にbmiを計算してから、bmiLevelIndicatorコントロールにセットしています。図らずも、キー値コーディングとそうでないものの違いがわかりますね。

- (void)awakeFromNib {   [self loadUserDefaults]; }

- (void)loadUserDefaults
{
  NSUserDefaults  *thisDefaults=[NSUserDefaults standardUserDefaults];
  if([thisDefaults floatForKey:@"lastHeight"]) {  
    [self setValue:[NSNumber numberWithFloat:[thisDefaults floatForKey:@"lastHeight"]] forKey:@"height"];
  }
  if([thisDefaults floatForKey:@"lastWeight"]) {
    [self setValue:[NSNumber numberWithFloat:[thisDefaults floatForKey:@"lastWeight"]] forKey:@"weight"];
  }
  if (weight > 0) {
    [self calculateBmi];
  }  
}



解答です。ソースコード(環境設定)部分です。



環境設定(NSUserDefaults)の使い方です。
awakeFromNibでNSUserDefaultsを読み込みます。ここでは、自分のloadUserDefaultsメソッドを実行させるだけです。
loadUserDefaultsメソッドで環境設定のlastHeightを読み込み、その値をheightキーにセットします。
weightも同様です。
最後にcalculateBmiを実施します。
これで起動したときのウィンドウに前回修了時の値が表示されます。



-(bool)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
{
  [self saveUserDefaults];
  return YES;    
}
- (void)saveUserDefaults
{
  NSUserDefaults  *thisDefaults=[NSUserDefaults standardUserDefaults];
  [thisDefaults setFloat:height forKey:@"lastHeight"];
  [thisDefaults setFloat:weight forKey:@"lastWeight"];
  [thisDefaults synchronize];
}

最後に環境設定の保存です。
アプリケーションが終了するとき(最後のウィンドウが閉じられるとき)に、saveUserDefaultsメソッドを実行します。
saveUserDefaultsメソッドではheightを環境設定のlastHeightにweightをlastWeightにセットして、synchronize(保存)します。
実際、

こんな感じで終わらせると、


こんな風に保存されます。
環境設定は、下記のファイルとして保存されています。
Macintosh HD:Users:xillion:Library:Preferences:com.yourcompany.Exercise7.plist
(yourcompanyはご愛嬌です)

ファイルの制御がこんなに簡単にできるのですね。

ひとつ前の投稿は「Cocoaはじめの一歩+α 練習問題5と6」です。
次の投稿は「applescriptガイド」です。

トラックバック

このエントリーのトラックバックURL:
http://www.xillion.net/mova/mt-tb.cgi/300

コメントを投稿

(いままで、ここでコメントしたことがないときは、コメントを表示する前にこのブログのオーナーの承認が必要になることがあります。承認されるまではコメントは表示されません。そのときはしばらく待ってください。)

About

2008年03月28日 22:30に投稿されたエントリーのページです。

メインページアーカイブページも見てください。

Powered by
Movable Type 3.34