Cocoa Break Logo Top Soft Develop  脱力空間 Logo
Apple Web Badge
made by mi

概要 翻訳 ソース リンク がらくた

概要 Examples ADC Samples 3rd Parties etc CBOriginals

DrawerMadness

以下は、Mac OS X 10.5.5 上の Xcode 3.1 で説明しています(2.5 以前からの変更点も注記しています。)。

目次:
1 README.rtf の日本語訳
2 アプリケーション
3 ファイル構成
4 MainMenu.nib
5 DrawerController クラス
5.1 宣言
5.2 初期化
5.3 ボタンによるドロアの開閉
5.4 ウインドウの拡大縮小時の動作
5.5 右側のドロアのサイズ変更動作
6 まとめ
7 更新履歴

1 README.rtf の日本語訳

DrawerMadness は、ドロア(引き出し)(drawer) のいくつかの異なる使用パターンを実演する小さなアプリケーションです。さらなる情報については、DrawerController.m 内のコメントを見てください。

2 アプリケーション

サンプルを起動すると次のようになります。

このウインドウにある「Open」ボタンでドロアがその場所に開き、「Close」ボタンで閉じる。「Toggle」ボタンはドロアの開閉を入れ替えます。場所と大きさが違うだけで、3 つのボタンの組の動作は同じです。右側のドロアは、一方を小さくすれば他方が大きくなります。

3 ファイル構成

プロジェクトファイルを見てみます。

注目する必要があるのは、DrawerController.h と .m、そして MainMenu.nib だけで、非常に簡単な構造です。

4 MainMenu.nib

次に MainMenu.nib を見てみます。接続線は表示していません。

Parent Window というのがメインウインドウです。この上にあるボタンは、マトリックス(NSMatrix)内に収められています。「Open」以下のボタンは、このマトリックス内でのボタンセル(NSButtonCell)になっています。このウインドウの委任が DrawerController に設定されています。

DrawerController は、File's Owner の delegate(委任)であり、このアプリケーションを実質的に動かすコントローラーです。上記のウインドウ内のボタンもこのインスタンスのアクションに接続されています。左側の「Open」ボタンセルからは openLeftDrawer:(左のドロアを開く)、「Close」ボタンセルからは closeLeftDrawer:(左のドロアを閉じる)、「Toggle」ボタンセルからは toggleLeftDrawer:(左のドロアを切り替える)で、残りのマトリックにおいても、Left の部分が Bottom になったりしているだけで、それぞれ 3 つのアクションに接続されています。また、このインスタンスは 2 つのアウトレットを持っていて、leftDrawer(左側のドロア)は NSDrawer に、myParentWindow は Parent Window に接続されています。

また、Parent Window のアウトレット delegate(委任)は、この DrawerController インスタンスに接続されています。すなわち、このインスタンスはアプリケーションの委任であるとともに、ウインドウの委任でもあります。これによってウインドウがサイズ変更された時など、委任メソッドが呼ばれることになります。

さて、新規 nib では見かけないものがあと 2 つあります。NSDrawer は NSDrawer クラスのインスタンスです。このクラスにはアウトレットが 3 つあり、インスペクタの接続ペインのアウトレットを見ると、contentView(内容ビュー)と delegate(委任)と parentWindow(親ウインドウ)です。このうち、contentView は、nib 内の DrawContentView という名前のビューに、parentWindow は先の Parent Window に接続されています。

DrawContentView は、単なるビューです。Interface Builder のパレットから Custom View を(ウインドウインスタンス内ではなく)nib ファイルウインドウ内にドラッグしたときに作られるものと、サイズが違う以外は同じです。内容は何もないものになっています。これは NSDrawer の内容ビューに接続されていたので、ドロア内に表示されるものになります。ここに、あらかじめボタン等を置いておけば、ドロア内にそれが表示されることになります。適当なテキストを置いて保存し、プロジェクトファイルでビルドして実行を行ってみましょう。そうすると、左側のドロアだけに文字が表示されたことがわかります。つまり、この DrawContentView は左側のドロアだけの内容ビューだということです。

5 DrawerController クラス

では、このサンプルの中心となる DrawerController クラスを見ていきましょう。

5.1 宣言

DrawerController.h は非常に単純で、継承とインスタンス変数を除けば、あとは nib で説明した 3 組のアクションメソッドが宣言されているだけです。

DrawerController.h > 継承とインスタンス変数
@interface DrawerController : NSObject { IBOutlet NSWindow *myParentWindow; // メインウインドウ IBOutlet NSDrawer *leftDrawer; // 左側のドロア NSDrawer *bottomDrawer; // 下側のドロア NSDrawer *upperRightDrawer; // 右上のドロア NSDrawer *lowerRightDrawer; // 右下のドロア }

ManMenu.nib のところで触れた 2 つのアウトレットが最初にあります。残りの 3 つは、左以外のドロアに対する参照です。

5.2 初期化

このクラスのインスタンスがアプリケーションの委任にされていましたが、NSApplication クラスの委任メソッドではなく、awakeFromNib 内で初期化が行われています。タイミングが問題な初期化がないからでしょう。

DrawerController.m > awakeFromNib
- (void)awakeFromNib { [self setupLeftDrawer]; // 左ドロアのセットアップ [self setupBottomDrawer]; // 下ドロアのセットアップ [self setupUpperRightDrawer]; // 右上ドロアのセットアップ [self setupLowerRightDrawer]; // 右下ドロアのセットアップ [self setBottomDrawerOffsets]; // 下ドロアのオフセット設定 [self setRightDrawerOffsets]; // 右ドロアのオフセット設定 }

基本的にこのクラスの独自メソッドを呼び出しているだけです。名前からわかるように、それぞれのドロアをセットアップし、下と右側のドロアのオフセットを設定しています。左側については、nib ファイル内で設定されているのでそのまま使います。まず、基本的な初期化を行っている setupBottomDrawer から見てみます。

DrawerController.m > setupBottomDrawer
- (void)setupBottomDrawer { NSSize contentSize = NSMakeSize(100, 100); bottomDrawer = [[NSDrawer alloc] initWithContentSize:contentSize preferredEdge:NSMinYEdge]; // 割り当てと初期化 [bottomDrawer setParentWindow:myParentWindow]; // 親ウインドウ設定 [bottomDrawer setMinContentSize:contentSize]; // 最小内容サイズ設定 [bottomDrawer setMaxContentSize:contentSize]; // 最大内容サイズ設定 }

まず、ドロアを割り当てて初期化し、インスタンス変数 bottomDrawer にポインタを入れています。初期化はサイズと場所で設定されていて、NSMinYEdge は名前のとおり Y が最小になる端で下端です。それから親ウインドウを設定し、その後、最小と最大サイズを設定しています。

サンプルを触ってみるとわかりますが、最小サイズといっても、ウインドウやビューなどのように、それ以上小さくできないサイズではありません。このサイズより小さくできます。ただし、設定された最小サイズ以下にされた時、ドロアは自動的に収納されてしまいます。下側のドロアではわかりにくいですが、左側のドロアは最大と最小サイズに幅があるので、最大まで引っぱって、それから少しずつ小さくしてやると、これを観察できます。その左側のドロアをセットアップする setupLeftDrawer を見てみましょう。

DrawerController.m > setupLeftDrawer
- (void)setupLeftDrawer { [leftDrawer setMinContentSize:NSMakeSize(100, 100)]; // 最小内容サイズ設定 [leftDrawer setMaxContentSize:NSMakeSize(400, 400)]; // 最大内容サイズ設定 }

このドロアは、MainMenu.nib 内で内容などが設定されていたので、初期化が行われていないことに注意してください。右側のドロアは、下側のものとほぼ同じですが、一部違います。setupUpperRightDrawer を見てみましょう。

DrawerController.m > setupUpperRightDrawer
- (void)setupUpperRightDrawer { NSSize contentSize = NSMakeSize(150, 150); upperRightDrawer = [[NSDrawer alloc] initWithContentSize:contentSize preferredEdge:NSMaxXEdge]; [upperRightDrawer setParentWindow:myParentWindow]; [upperRightDrawer setDelegate:self]; // 委任設定 [upperRightDrawer setMinContentSize:NSMakeSize(50, 50)]; }

setupBottomDrawer とほぼ同じですが、委任が設定されているのが違います。これで、このドロアを動かした時などに、委任メソッドが呼ばれることになります。右下のドロアは、これと同じです。

次にオフセット設定メソッド setBottomDrawerOffsets を見てみましょう。

DrawerController.m > setBottomDrawerOffsets
- (void)setBottomDrawerOffsets { NSSize frameSize = [myParentWindow frame].size; // ウインドウサイズ取得 [bottomDrawer setLeadingOffset:50]; // 先頭オフセット設定 [bottomDrawer setTrailingOffset:frameSize.width - 270]; // 末尾オフセット設定 }

オフセットについては、ガイドの『ドロア』の「ドロアの配置とサイズ設定」の図を見てください。ここで、先頭オフセットが固定で、末尾オフセットが、ウインドウのフレームサイズから、固定サイズを引いた値に設定されていることに注意してください。このため、ウインドウが大きくなっても、ドロアの位置とサイズは変化しません。サンプルでウインドウを大きくしてみれば、左側のドロアの高さは高くなりますが、下側のドロアは変化しないでしょう。右側のドロアは、違う形で設定されています。setRightDrawerOffsets を見てみます。

DrawerController.m > setRightDrawerOffsets
- (void)setRightDrawerOffsets { NSSize frameSize = [myParentWindow frame].size; // ウインドウサイズ取得 unsigned int halfHeight = frameSize.height / 2, // ドロアサイズ計算 remainder = frameSize.height - 2 * halfHeight; // 余りのサイズ計算 [upperRightDrawer setLeadingOffset:50]; [upperRightDrawer setTrailingOffset:halfHeight]; [lowerRightDrawer setLeadingOffset:halfHeight]; [lowerRightDrawer setTrailingOffset:50 + remainder]; }

下側のドロアと違うところは、半分のサイズを計算し、それを上側の下のオフセットと、下側の上のオフセットに設定し、また、上端は固定ですが、下端は固定と余分のサイズを足したものに設定しています。これによって、ほぼ半分で上下に二分されることになり、きちんと半分にできない場合、その余分は下端のオフセットに吸収されることになります。

これで初期化は終わりました。これでアプリケーションは待ちの状態に入ります。ボタンが押されてアクションメソッドが呼ばれるか、ウインドウサイズが変更されて委任メソッドが呼ばれるか、または右側のドロアが動かされて委任メソッドが呼ばれるまで、このクラスでは特に何も行いません。

5.3 ボタンによるドロアの開閉

まず、ボタンセルから呼ばれるアクションメソッドを見てみましょう。「Open」のアクションメソッドは簡単です。

DrawerController.m > openLeftDrawer:
- (void)openLeftDrawer:(id)sender {[leftDrawer openOnEdge:NSMinXEdge];}

インスタンス変数で保持しているドロアに対して開くように指示しているだけです。「Close」も似たようなものです。

DrawerController.m > closeLeftDrawer:
- (void)closeLeftDrawer:(id)sender {[leftDrawer close];}

「Toggle」は以下のようになります。

DrawerController.m > toggleLeftDrawer:
- (void)toggleLeftDrawer:(id)sender { NSDrawerState state = [leftDrawer state]; // ドロア状態取得 if (NSDrawerOpeningState == state || NSDrawerOpenState == state) { [leftDrawer close]; // 閉じる } else { [leftDrawer openOnEdge:NSMinXEdge]; // 開く } }

まず状態を取得し、それが開いているか、開いている最中なら閉じます。閉じているなら開きます。

5.4 ウインドウの拡大縮小時の動作

さて、DrawerController は、MainMenu.nib 内でウインドウの委任に設定されていたので、ウインドウの委任メソッドが呼ばれることになります。このクラスでは、windowDidResize: を実装しています。これは、ウインドウがサイズ変更された後で呼び出されます。

DrawerController.m > windowDidResize:
- (void)windowDidResize:(NSNotification *)notification { [self setBottomDrawerOffsets]; [self setRightDrawerOffsets]; }

処理は簡単で、オフセットを再設定しています。上の説明であったように、ドロアのオフセットは、下側の場合、右側のオフセットが、右側のドロアの場合、上下に 2 分するために、ウインドウのフレームサイズから計算されていました。そのため、ウインドウがサイズ変更された時に再計算する必要があります。このために、オフセット設定メソッドだけが分離されていたわけです。左側のドロアは、再計算がなく、もともとのオフセットによって拡大縮小されることになります。通常は、特にオフセットを再計算する必要はないでしょう。

5.5 右側のドロアのサイズ変更動作

サンプルをいじるとわかるように、右側のドロアは一方のドロアを大きくすると他方が縮むようになっています。右側のドロアのセットアップで、DrawController(self)がドロアの委任にされていたことを思い出してください。これによって、ドロアの委任メソッドが呼ばれることになります。drawerWillResizeContents: は、ユーザーがドロアまたは親をサイズ変更したとき呼び出されます。

DrawerController.m > drawerWillResizeContents:
- (NSSize)drawerWillResizeContents:(NSDrawer *)sender toSize:(NSSize)contentSize { contentSize.width = 10 * ceil(contentSize.width / 10); // 内容サイズ幅を10の倍数に if (contentSize.width < 50) contentSize.width = 50; // 小さすぎるとき if (contentSize.width > 250) contentSize.width = 250; // 大きすぎるとき if (sender == upperRightDrawer) { // 右上なら [lowerRightDrawer setContentSize: NSMakeSize(300 - contentSize.width, // 300 から現在幅を引いた値 [lowerRightDrawer contentSize].height)]; } else if (sender == lowerRightDrawer) { // 右下なら [upperRightDrawer setContentSize: NSMakeSize(300 - contentSize.width, [upperRightDrawer contentSize].height)]; } return contentSize; }

まず、内容サイズを 10 の倍数にしています。これにより、10 きざみでドロアのサイズ変更が行われることになります。ceil はそれより大きい最も近い整数にします。これにより、内容サイズ幅 51 は 60 となり、59 も 60 となり、61 は70 になります。したがって、大きくされる時は少しでも動かせば大きくなり、逆に小さくされる時は少し動かしても変わりません。

次に大きすぎたり、小さすぎる時は、再設定しています。最小サイズや最大サイズを設定していましたが、ユーザーインターフェイス項目の最小や最大サイズ設定は、基本的にユーザーの操作に制約を課すもので、プログラム上での変更に対しては適用されないことが多いことに注意してください。プログラム上でも、ユーザーが操作したかのように設定する類のメソッドは、呼び出したときサイズ制限を適用してくれます。それ以外で、プログラム上でサイズ変更を行うとき、最小や最大サイズ制限を適用したいなら、自分でやらなければなりません。

最後に右上か右下かをチェックして、右上なら右下のサイズを、右上の変更したサイズにしたがって再設定しています。300 から引いているので、最大の 250 なら、もう一方は 50、最小の 50 なら、もう一方は 250 となり、片方が大きくなると片方が小さくなるようになっています。高さはそのままにされます。

6 まとめ

このサンプルでは、Interface Builder による作成と、プログラム上での作成の 2 つの方法が示されました。通常は、Interface Builder 上で作成することになると思います。ドロアインスタンスを作り、ビューを nib ウインドウ内に作って、そこに項目を配置し、ドロアインスタンスの内容ビューを作成したビューにして、ドロアインスタンスの親ウインドウを設定します。思ったようにドロアが表示されない場合、接続をチェックしてください。

7 更新履歴

2007.11.17

v10.4、Xcode 2.5 で解説作成(ずっと以前の自分用のメモを修正)。

2008.10.13

v10.5.5、Xcode 3.1 用に解説更新。


管理人:神吉 秀典 E-mail:puer@ba.wakwak.com