ウィンドウを拡げるとおかしな事に…
ウィンドウのサイズを大きくすると下の図の様になってしまうのですが、無駄な余白が上側と右側にできてしまって何とも間が抜けた状態です。OscillatorViewが一つだけ存在して、かつ形状が正方形でなくてもよいならば、autoresizingMaskをInterface Builderで設定するだけで何とかなるのですが、OscillatorViewが二つ、LissajousViewが一つあって互いの位置関係を保ちながら拡大・縮小したいとなると話はそう単純ではありません。
OscillatorViewとLissajousViewがそれぞれの位置関係を保ちながらちょうど良い大きさにする機能を持たせたビューEnclosureViewを作って、この問題を解決します。

新規ファイルでNSViewのサブクラスを作る
Xcodeのファイルメニューから「新規ファイル…」を実行します。CocoaグループのObjective-C NSView subclassを選択して「次へ」をクリックします。

EnclosureViewと入力して完了です。

アウトレットとインスタンス変数を追加する
アウトレットを追加する
OscillatorViewとLissajousViewのサイズを変更しなくてはいけないのですから、それらにアクセスできないといけません。簡単なのはアウトレットを追加する事です。そこでアウトレットを三つ追加します。
OscillatorViewやLissajousView固有の機能を使うわけではありませんから、NSViewとしてアウトレットを作成しておきます。これなら前方参照やインターフェイスファイルをインポートする必要はありません。
インスタンス変数を追加する
周囲の余白(whiteSpace)とOscillatorView - LissajousView間の距離(viewMargin)を設定するインスタンス変数を追加します。
//
// EnclosureView.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface EnclosureView : NSView
{
IBOutlet NSView *xView, *yView, *lissajousView;
int whiteSpace,viewMargin;
}
@end
初期化
インスタンス変数の初期化と通知の設定
初期化コードで実行する事は、インスタンス変数の初期化と通知を受け取る様に設定する事です。自分がリサイズされた事を知りたいわけですから、NSViewFrameDidChangeNotificationを受ける様にします。objectとしてselfを指定しているので、自分のサイズが変わった通知だけを受け取る事になります。
通知を受ける登録をしたら、解除の事も考えないといけません。登録解除はdeallocメソッドで行ないます。
起動時の処理
リサイズされて初めて通知を受け取るわけなので、起動時は通知を受け取れません。したがって起動時は、OscillatorViewとLissajousViewのサイズはInterface Builderで設定されたサイズのままです。Interface Builderできっちり設定しておけばいいのですが、それも面倒なので起動時にもサイズ調整する様にしておきます。
initメソッドが呼ばれる時点ではアウトレットが接続されている保証はないので、awakeFromNibでサイズ調整します。
//
// EnclosureView.m
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import "EnclosureView.h"
@interface EnclosureView(Private)
- (void)frameDidChange:(NSNotification *)aNotification;
@end
@implementation EnclosureView
#pragma mark -
#pragma mark Initializer and Deallocator
- (id)initWithFrame:(NSRect)frame
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
self = [super initWithFrame:frame];
if(self)
{
whiteSpace = 4;
viewMargin = 8;
[nc addObserver:self
selector:@selector(frameDidChange:)
name:NSViewFrameDidChangeNotification
object:self];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)awakeFromNib
{
[self frameDidChange:nil];
}
@end
自分がリサイズされた時の処理
まずサブビューのサイズを計算します。widthはサブビューに許される最大幅です。これはEnclosureViewの幅から両脇の余白(whiteSpace*2)を引き、サブビュー間の距離(viewMargin)を引いて2で割ればでます。2で割っているのは横方向にサブビューが2個(Y軸のOscillatorViewとLissajousView)あるからです。
同じ様にサブビューに許される最大高さheightを計算します。widthとheightのどちらか小さい方がサブビューのサイズになります。
サブビューのサイズがでたら、サブビューがセンタリングされる様に左側と下側のマージンを計算します。
計算結果に基づいて各サブビューの原点とサイズを決め、配置して終了です。
//
// EnclosureView.m
//
@implementation EnclosureView(Private)
#pragma mark -
#pragma mark Event Handler
- (void)frameDidChange:(NSNotification *)aNotification
{
int width,height,viewSize,marginWidth,marginHeight;
NSSize sizeOfView;
NSPoint originViewX,originViewY,originViewLissajous;
width = ([self bounds].size.width - whiteSpace*2 - viewMargin)/2;
height = ([self bounds].size.height - whiteSpace*2 - viewMargin)/2;
viewSize = width < height ? width : height;
marginWidth = ([self bounds].size.width - viewSize*2 - viewMargin)/2;
marginHeight = ([self bounds].size.height - viewSize*2 - viewMargin)/2;
originViewY.x = marginWidth;
originViewY.y = marginHeight;
originViewX.x = originViewY.x + viewSize + viewMargin;
originViewX.y = originViewY.y + viewSize + viewMargin;
originViewLissajous.x = originViewX.x;
originViewLissajous.y = originViewY.y;
[xView setFrameOrigin:originViewX];
[yView setFrameOrigin:originViewY];
[lissajousView setFrameOrigin:originViewLissajous];
sizeOfView = NSMakeSize(viewSize,viewSize);
[xView setFrameSize:sizeOfView];
[yView setFrameSize:sizeOfView];
[lissajousView setFrameSize:sizeOfView];
}
@end
【コラム】カテゴリについて
ソースコードを注意深く見ていた方は気づいたと思いますが、frameDidChange:メソッドはPrivateという名前のカテゴリの中に入っています。これはインターフェイス部に宣言されていない局所的なメソッドが、その定義より前のコードから使えない(警告が出る)問題を解決するための手段としてカテゴリを使っている例です。
Cのプロトタイプ宣言と同じ効果があると言った方がわかりやすいかもしれません。
awakeFromNibより前にframeDidChange:を移動させれば済む話なのですが、初期化関連のメソッドよりも前にこのようなごちゃごちゃしたメソッドが来るのはどうにも気持ちが悪いので、カテゴリを使いました。

ホーム
前へ