こんにちは、iOS/Mac アプリ開発担当の宮本です。
最近は、Sleipnir Mobile for iPhone と Sleipnir for Mac を開発しています。
Sleipnir for Mac の開発では、UIKit と AppKit の違いに苦戦しています。
出てくるクラス名も接頭辞が違うだけのものが多いので、ほとんど変わらないと思っていたのですが、ふたを開けてみると全くの別物でした。
今回は、その中でベースのビューとなるクラス NSView と UIView の違いについて紹介します。
これから Mac アプリを開発する方は参考にしてみてください。
■ NSView には見た目に関するプロパティが全然ない
NSView のクラスリファレンスを見るとビックリするぐらい UIView にあるプロパティがありません。最初、backgroundColor がないのには驚きました。ただ背景色をつけたいだけでサブクラスで drawRect: をオーバーライドです。他にも影やアルファがなくて困ります。
■ NSView は兄弟同士を重ねてはいけない
Mac OS X の View Programming Guide に次のような記述があります。
Note: For performance reasons, Cocoa does not enforce clipping among sibling views or guarantee correct invalidation and drawing behavior when sibling views overlap. If you want a view to be drawn in front of another view, you should make the front view a subview (or descendant) of the rear view.
UIView と同じような描画結果は保証されていないようです。UIView では UIImageView を一番下や一番上に貼って、背景や影を実現することがありました。NSView だと無理です。画像はなるべく drawRect: で描画して、上にのせるような View はきちんと subview にする必要があります。UIView では subview を配置しまくって、見た目を実現することが多かったのですが、NSView では基本的に drawRect: で見た目を作るしかありません。
■ NSView はアニメーションが全然できない
NSView のアニメーションのためのクラス NSViewAnimation というものがあります。ただ、できるのは frame の変更と、フェードイン・フェードアウト だけ。
一方、UIView はほとんどのプロパティ書き換えをアニメーションできます。
■ NSView は Layer-Backed View じゃない
色々文句を書きましたが、これだけ色々違いがあるのは NSView が Layer-Backed View じゃないからです。Layer は CALayer のことで、Mac OS X 10.5 Leopard で追加された Core Animation というフレームワークのものです。
Core Animation を使うと、滑らかでパフォーマンスの良いアニメーションが容易に実現できます。UIKit がアニメーションを簡単に実現できるのは、Core Animation をベースに作られているからで、UIKit の drawRect: は、実際 Core Animation の描画をしているだけです。
一方、NSView は Core Animation とは違う描画方式なので、アニメーションも容易ではありません。しかし、次のコードを実行することで Layer-Backed View になることができます。
[view setWantsLayer:YES];
Layer-Backed View になると、NSView の drawRect: が呼ばれなくなって、代わりにバックエンドの Layer の drawInContext: が呼ばれるようになり、Core Animation ベースの描画方式に変わります。これで、背景色、アルファ、影などが簡単に設定できます。
じゃあ、全部 Layer-Backed View にしてしまえば UIView みたいに使えるんじゃないのと思いますが、そう上手くはいきません。Layer-Backed View にすると、subview が全て Layer-Backed View になってしまいます。しかし、AppKit の View が全て Layer に対応しているわけでもなく、たまに描画がおかしくなる View があります。他にも drawRect: で描画している独自のカスタムクラスは機能しません。 なので、必要なところだけ Layer-Backed View を使うことになります。
■ Layer-Backed になっても NSView と UIView はちょっと違う
もう一点 Layer-Backed View には問題があって、NSView と UIView では frame の変更による挙動が少し違います。
UIView では Layer の frame を変更すると、それに合わせて、UIView の frame も変更されますが、NSView では Layer がついてきてくれません。それぞれ別の frame を持つことになります。
つまり、Layer-Backed View は移動アニメーションのためには使えないということです。アプリで使うアニメーションはほとんど frame の変更なので、あまりうれしくありません。調べてみると、Layer-Backed View は影かアルファかトランスフォーム(回転とか拡大縮小)したい時に使うもののようです。
■ Sleipnir for Mac のタブは NSView を使わずに CALayer のみで実装
Sleipnir for Mac のタブバーは横にスクロールできます。レイアウト時にはスクロール位置を動かすアニメーションをさせています。しかし、NSScrllView にはスクロールをアニメーションする方法はありません。スクロールビューだけでなく、UIKit のメソッドによくある animated: の引数が AppKit にはありません。
一方、CALayer のサブクラスに CAScrollLayer というクラスがあって、それはスクロールをアニメーションさせられます。
Sleipnir for Mac では、タブバー全体を Layer-Backed な NSView にして、その sublayer に CAScrollLayer を、そのまた sublayer にタブをたくさん配置することで、あのアニメーションを実現しています。スクロール以外でも、CALayer を使うと暗黙のアニメーションをしまくってくれるので、タブのハイライト切り替えや、サムネイルの変更でさえアニメーションしてくれます。
CALayer はアニメーションさせると強力なのですが、CALayer にも大きな問題があって、マウスイベントやタッチイベントを処理できません。NSView でないとイベントを処理できないので、Sleipnir では一つの Layer-Backed な View がタブそれぞれのイベントを処理しています。そんな CALayer にイベント処理の仕組みをのせたのが UIView です。
■ iOS ライクなフレームワーク
CALayer をベースに使うと、Sleipnir のようなタブバーが実現できますが、イベント処理が面倒です。しかし、これから紹介するフレームワークを使えば楽ができるかもしれません。
1. Chameleon Project
Twitterrific を開発する Iconfactory のオープンソースプロジェクトで、iOS のソースコードを Mac でそのまま再利用するフレームワークです。UIKit の API が使えるのでもちろん Layer プログラミングができます。
2. TwUI
Twitter for Mac で使ってる UI フレームワーク を先日公開してくれました。
その時のブログのエントリーに、Core Animation は素晴らしいけど、NSView のイベントの仕組みがないから、問題だという話をしてます。このフレームワークを使えば、CALayer をベースにコードを書けますし、NSView 並のイベント処理ができるようになっています。クラス名は違いますが、API もほとんど UIKit と同じです。
また、TwUIView では draw と layout がブロックで書けるようになっていて、サブクラスを書かなくてもちょっとした描画とレイアウトができるのも特徴です。
どちらのフレームワークも CALayer を使って、UIKit 的なものを作っています。イベント処理に関しては、一つベースとなる Layer-Backed View がいて、該当する Layer に対してイベントを送っているようでした。UIView の実装はほとんど、中の CALayer のプロパティをいじっているだけで、drawRect: も実際は Layer の描画です。ソースコードを読むと、iOS の UIKit もこういう実装なのかと思えたりしてなかなか楽しめるので、一度見てみてください。
最近の Mac アプリは iOS ライクなものが多いですが、AppKit ではそういうアプリは作りにくいです。Lion で加わった API で少しは良くなりそうですが、まだまだです。そういったアプリを作りたい方は、これらのフレームワークを検討してみるといいかもしれません。
■ まとめ
・ NSView と UIKit は描画が全然違う
・ UIKit は Core Animation ベースの描画
・ NSView も Core Animation ベースにできるが、ちょっと違う
・ Core Animation にイベント処理をつけたのが UIKit
・ Mac で iOS みたいなことがしたかったら、上のフレームワークを使うと良い
Sleipnir では Layer を多用しそうなので、Chameleon Project や TwUI みたいな仕組みを作っていきたいと考えています。