Cocoa Programming Tips 1001
Application Kit
NSScrollView
Application Kit - NSScrollView
スクロールビューの構造
Keywords: content view, document view
NSScrollView は、他の View を Subview として取り込んで、スクロールバー付きで表示できる View だ。その構造を調べてみよう。
例として、Interface Builder でスクロールビューを作ってみる。Custom View を貼付けて、[Layout] -> [Make subviews of] -> [Scroll View] で作ることができるんだ。

さて、このとき View 階層はどうなっているか?調べてみると、こんな感じになっている。
- NSScrollView
- NSClipView
- NSView
- NSScroller
- NSScroller
NSClipView の中に NSView があるのが分かるよね。NSView が、実際にスクロールされる View だ。
で、Scroll View では、この NSClipView を Content View、スクロールされる View を Document View って呼んでいるんだ。それぞれ、contentView、documentView で取り出すことができる。
Application Kit/NSScrollView.h
- (NSClipView *)contentView;
- (id)documentView;
contentView は NSClipView を返しているのに対して、documentView は id 型で返しているのに注意。
あと、他に含まれている 2 つの NSScroller は、縦と横のスクロールバーだね。
Application Kit - NSScrollView
左上固定のスクロールビュー
Keywords: NSClipView, isFlipped
NSScrollViewを使えば、簡単にスクロールバーつきのビューを実現できる。だけど、落とし穴が一つ。Cocoaの座標系は原点左下なので、スクロールビューも左下固定になってしまうのだ。これは使いにくい。やっぱり、左上固定にしてほしいよね。

NSViewには、原点を左下から左上に変更するために、isFlippedというメソッドが用意されている。おそらく、これを上書きすればいいんだけど、さてどのクラスでやればいいのか?
まず、documentViewでやる方法がある。documentViewになるクラスで、isFlippedを上書きしてYESを返すようにする。これで、左上固定になる。
ただし、documentViewが左下原点を前提に作られていたり、反転するとなにかと不具合がおこる場合もあるだろう。というわけで、ここではcontentViewで上書きする方法をやってみる。NSScrollViewのcontentViewである、NSClipViewのサブクラスを作る。このサブクラスで、isFlippedを上書きするのだ。
サブクラスを作る事自体は簡単だ。ただ、これをNSScrollViewに埋め込むのがちょっと面倒。普通に作ると、NSScrollViewは通常のNSClipViewを使う。そこで、初期化が終わった段階で、NSClipViewを新たに作ったサブクラスでスワップしてやることにする。
実際にやってみよう。まず、NSClipViewのサブクラスFlippedClipViewを作る。このクラスではisFlippedを上書きして、YESを返す。これで、左上が原点になる。
FlippedClipView.m (sample)
- (BOOL)isFlipped
{
return YES;
}
次に、アプリケーションのコントローラとなるクラスの中で、contentViewのスワップを行う。こんな感じで。
AppController.m (sample)
- (void)awakeFromNib
{
// Swap clip view
NSClipView* oldClipView;
FlippedClipView* newClipView;
oldClipView = [_scrollView contentView];
newClipView = [[FlippedClipView alloc] initWithFrame:[oldClipView frame]];
[newClipView setDrawsBackground:[oldClipView drawsBackground]];
[newClipView setBackgroundColor:[oldClipView backgroundColor]];
[newClipView setDocumentView:[oldClipView documentView]];
// Move subviews
NSEnumerator* enumerator;
NSView* subview;
enumerator = [[oldClipView subviews] objectEnumerator];
while (subview = [enumerator nextObject]) {
[subview removeFromSuperview];
[newClipView addSubview:subview];
}
// Set content view
[_scrollView setContentView:newClipView];
[newClipView release];
}
ソースコード中でFlippedClipeViewのインスタンスを作っている。ポイントは3つ。1つは、古いNSClipViewの設定をすべて移す事。これらの設定は、Interface Builderで行われている。2つは、サブビューも移動してやる事。そして3つは、setContentViewとsetDocumentViewを呼び出して、関連付けを行っておくこと。
こうしておけば、documentViewに関わらず、左上固定のスクロールビューを実現することができる。

■サンプルダウンロード:
LeftUpperScroll.zip