BathyScaphe ネタ(その11)

今日も BathyScaphe の話。
BathyScaphe のウインドウ、右下隅を掴んでリサイズしようとすると……ガク、ガク、ガクッッ…と、結構「重い」コトにストレスを感じる方も結構多いのではないだろうか?何を隠そう私自身がストレスを感じている*1。反応が悪いため、ビシッと決めたい位置に決めるのも難しくなってしまうし、この動作一つが重いばかりに、アプリケーション全体が「あぁ…コイツはモッサリ型だ…」というイメージを植え付けかねない。ある日とうとう無視できなくなって、対処法を考えてみることにした。


ライブリサイズ時のもたつきの原因が、NSTextView −もっと正確に言えば、NSTextView のリサイズに追従して、中身のテキストがリアルタイムでレイアウトし直される挙動にあるのは明らかだ。リサイズの合間によいしょよいしょとレイアウトをやり直すから、負担が大きい。…では、それを止めてはどうか?と考え、NSTextView のサブクラスに以下のようなコードを書いた:

- (void) viewWillStartLiveResize
{
	[[self textContainer] setWidthTracksTextView: NO];
	[[self textContainer] setHeightTracksTextView: NO];
	[super viewWillStartLiveResize];
}

- (void) viewDidEndLiveResize
{
	[[self textContainer] setWidthTracksTextView: YES]; // (1)
	[[self textContainer] setHeightTracksTextView: YES]; // (2)
	[super viewDidEndLiveResize];
}

ライブリサイズ中は、テキストコンテナのサイズをテキストビューに追従させない、つまり再レイアウトを行わないようにしようという考えだ。しかし、このコードは失敗した。確かにライブリサイズ中は再レイアウト作業は行われない。しかしリサイズが終わった後も、再レイアウト作業が行われないままなのだ。よく考えれば、当たり前である。リサイズが終わってから*2いくら上の (1) (2) が呼び出されても、意味がない。
じゃあ強制的に再レイアウトさせよう、と思ったのだが、そのためのメソッドがありそうでなかなか見つからない。探して探して、ようやくそれらしいのを NSLayoutManager で見つけてきた:

- (void)textContainerChangedGeometry:(NSTextContainer *)aTextContainer

ドキュメントには This method is invoked automatically by other components of the text system; you should rarely need to invoke it directly. なんて書いてあるので少し気が引けるけれども、これを呼んでやることで強制的に再レイアウトを起こすことができる。先ほどの -viewDidEndLiveResize: 内にコイツを書き足してやることで、どうやら、意図した通りの動きをするようになった*3。ライブリサイズも、スーイスーイとまではいかないけれども、グッ…スススススススッ、パッ。くらいにはなったぞ(?)。まぁ、こういうアプローチ、方法でいいのかどうかはよくわからない*4。もっとスマートか、定番の方法があれば、教わりたいところである。

*1:もっと高性能な Mac では気にならないのかもしれないが

*2:呼び出されるタイミングが、willEnd じゃなくて、DidEnd だからね…

*3:なお、実際のソースではもう少し別の書き方になっている。CMRThreadView.m と BSLayoutManager.m をチェックだ

*4:動作自体には問題はないはず。実装の観点から、という意味である