![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ||
![]() | ![]() | ![]() | ![]() | ![]() |
|
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
以下は、Mac OS X 10.5.5 上の Xcode 3.1 で説明しています(2.5 以前からの変更点も注記しています。)。
| 1 README.rtf の日本語訳 2 アプリケーション 3 ファイル構成 4 MainMenu.nib 5 DotView クラス5.1 インスタンス変数 5.2 ビュ−の初期化と解放 |
5.3 描画の実行 5.4 スライダーに対する応答 5.5 カラーウェルに対する応答 5.6 マウスイベント処理 6 まとめと機能追加のアイデア 7 更新履歴 |
DotView は、以下のことのための NSView の非常に単純なサブクラスを実演する小さなアプリケーションです。
さらなる情報については、DotView.m 内のコメントを見てください。また、アプリケーションにおける DotView のインスタンスと、それにつながれているコントロールを見るためには、Interface Builder の(nib)ファイル(MainMenu.nib)も参照してください。
サンプルを起動すると、次のようなウインドウが表示されます。
![]() |
下のスライダーを右に動かすと、ドットの大きさが大きくなります。また右下のカラーウェルによって、色を変更することもできます。またビューのなかでマウスをクリックすると、その場所にドットが移動します。
![]() |
では、プロジェクトを構成しているファイルを見ていきましょう。まずプロジェクトファイルを見てみます。
![]() |
Classes に、DotView.h と .m がありますが、これは NSView のサブクラスで、ウインドウ内にある主要なビュ−になります。
Resources に、AppIcon.icns という画像がありますが、これはアプリケーションのアイコンです。MainMenu.nib には、ユーザーインターフェイス等の定義があります。
![]() |
MyWindow インスタンスのなかに、DotView クラスのインスタンスがあります。この nib ファイル内にコントローラーが存在しないことに注意してください。また、アプリケーションの委任も設定されていません。ウインドウは「起動時に表示」がチェックされています。ウインドウ内には、スライダーがあり、これは DotView の setRadius: アクションメソッドに接続されています。また、右下のカラーウェルからは、DotView の setColor: アクションメソッドに接続されています。
コントローラーがないので、スライダーやカラーウェルの操作は、アクションによって直接 DotView に伝えられます。
DotView クラスまず、DotView クラスについて調べてみましょう。DotView.h での宣言は次のようになっています。
インスタンス変数はドットの中心の位置を格納する center と、色を格納する color と、ドットの大きさを表す radius です。
古いサンプルでは、radius は float 型でした。Mac OS X v10.5 から、64 ビット化に対応するために、浮動小数点数には CGFloat が使われています。こうした変更については、『Cocoa のための 64 ビット移行ガイド』を見てください。整数型も変更されているので、古い資料などを参考にする場合は注意が必要です。
独自ビューの場合、nib ファイルから読み込まれた時に、ビューの初期化メソッドが呼ばれます。そこで必要な初期化を行うことができます。これは initWithFrame: です。
ここでは、スーパークラスの初期化メソッドを呼び出しているほかは、必要なインスタンス変数を初期化しているだけです。ちなみに、初期化が失敗したときに備えて、以下のように変更したほうがより安全です。
こういう形で初期化メソッドを書くクセをつけておくのが無難です。
上記の初期化メソッドで、color に設定されたオブジェクトが retain されていることに注意してください。このため、dealloc のなかで、これを release しなければなりません。
このように、dealloc 内では最後に必ず super の実装を呼び出します。
起動時に表示がチェックされていたので、初期化が終了した後、ウインドウが表示されます。この時に描画メソッドも呼び出されることになります。その後で、アプリケーションは待ちの状態に入ります。
実際の描画は、drawRect: のなかで行われます。
まず白色で背景を塗りつぶしています。NSRectFill は AppKit 関数で、引数の長方形内を塗りつぶします。コメント内にあるように、ベジエパスを作って塗りつぶしてもかまいません。クラスメソッド bezierPathWithRect: は引数の長方形の形をしたパスを返します。これは自動解放オブジェクトです。その後 fill でそのパスを塗りつぶします。塗りつぶしの前に色が設定されていることに注意してください。
次にドットの範囲を計算しています。center から縦横に半径を引いた場所に原点を置き、サイズを半径の 2 倍に設定しています。
そして円を描画します。bezierPathWithOvalInRect: は、引数の長方形いっぱいの円形パスを返します。ここで、長方形は縦横が違っていてもかまいません。その場合、楕円が返されることになります。ここでは、幅と高さが同じなので正円が返されることになります。
描画関係メソッドで、isOpaque が実装されていることに注意してください。これは以下のようになっています。
isOpaque はデフォルトで NO を返すようになっています。このメソッドは、ビューが不透明かどうかを教えます。Mac OS X では、半透明のウインドウやビューが可能なので、ビューが半透明なら、その背後にあるウインドウやビューから描画を始める必要があります。そのため、描画システムにそれを教えるために、このメソッドがあります。これで YES を返すことで、このビューの背後の描画は気にしなくてよくなるので、描画のパフォーマンスが向上します。『Cocoa のためのビュープログラミングガイド』>「ビューの描画の最適化」>「ビューの不透明度の指定」を見てください。
サンプルが起動され、ウインドウが表示され、その結果ビューが描かれた後は、アプリケーションはユーザーの行動を待っている状態に入ります。ユーザーがスライダーを動かしたり、カラーウェルで色を変更したり、ビューの上でマウスを動かしたとき、それぞれに対応するメソッドが呼ばれ、アプリケーションは応答することになります。
スライダーの処理を見てみましょう。MainMenu.nib の説明で書いたように、スライダーはアクション setRadius: に接続されていました 。
このアクションはスライダーを動かした時に呼ばれます。値が変更されたということなので、スライダーから浮動小数点値を取得し、それをインスタンス変数 radius に格納しています。次に、このビューが再表示されたほうがいいことをマークしておきます。こうしておくと、適切なタイミングでウインドウシステムが自動的に再表示を行ってくれます。ビューには display... 系メソッドがあり、すぐに再表示を要求できます。しかし、スクリーンにはリフレッシュレートがあり、結局のところ、どれほど頻繁に表示を変更しようと、画面自体はそのタイミングでしか更新されません。自動更新は、リフレッシュレートにあわせて表示を用意することで、無駄な作業を減らすようになっています。通常は、この自動更新を使ったほうがいいでしょう。これについては、『Cocoa のためのビュープログラミングガイド』>「ビュー内容の描画」を見てください。インスタンス変数 radius を変更したので、次の描画のとき、上で見たように、その値によって円の範囲が計算され、スライダーを反映した円が描画されることになります。
古いサンプルでは radius が float だったため、[sender floatValue] が使われていました。Xcode 3.0 以降では、radius が CGFloat となり、これは 64 ビット環境では double 値となるので、doubleValue に変更されています。
カラーウェルから呼び出されるのは、以下のアクションメソッドです。
これはごく単純です。引数の sender には呼び出し側のカラーウェルが入っているので、色を問い合わせて、それを設定しているだけです。スライダーの場合と同様に再描画が必要であることを指示しています。
簡単なマウスなどのイベント処理は、MouseDown: や MouseUp: などのメソッドをオーバーライドすることで行えます。これは NSView のスーパークラスである NSResponder によって宣言されています。DotView では、このうち mouseUp: がオーバーライドされています。
まず最初にイベントの起こった場所を取得します。この位置はウインドウ座標なります。次に、その位置をウインドウ座標からこのビュー内の座標へと変換します。convertPoint: fromView: は、2 番目の引数のビュー座標で表された、最初の引数の点を、レシーバーのビューの座標へと変換します。このとき 2 番目の引数に nil を指定すれば、ウインドウの基準座標系として扱われることになります。そして、変換した座標を円の中心を保有するインスタンス変数 center に入れています。そして、スライダーの場合と同様に再描画が必要であることを指示しています。
このサンプルは、基本的なもので、ビューのサブクラス作成を説明しています。ここで注目するのは、スライダーやカラーウェルは、単にインスタンス変数の値を変更しているだけで、それとは別に描画が行われていることです。手続き的なやり方に慣れている人には、最初はこのような形での描画を不安に思うかもしれません。しかし、イベントループを通るたびに、自動表示チェックが行われていて、リフレッシュレートにあわせて表示が行われるので、描画がそれほど遅れるわけではありません。
このサンプルは、ある意味、M-V-C パターンのうちコントローラーがない、M-V パターンを示しています。とはいうものの、コントローラーは背後に隠れている Cocoa システムがになっているわけです。スライダーやカラーウェルは、ビューを変更するわけではなく、そのデータモデルを持っているインスタンス変数を変更し、ビューが再描画するときに、モデルにデータが問い合わせられます。とはいえ、再表示が必要なことを指示する部分で、直接ビューとやりとりしているので、この図式は微妙に違いますが、基本的に他の部分で直接描画にかかわることを避けることで、描画部分の分離に成功しています。
このサンプルは基本的なものなので、機能追加のアイデアは出しません。基本的すぎて、何でも追加して試すことができます。環境設定で前に描画した円の情報を残すようにしてみたり、アイデアは無限にあるでしょう。サンプルには、他に DotViewUndo が追加されています。これは取り消し機能が追加されたものです。
| 2007.11.12 | v10.4、Xcode 2.5 で解説作成(ずっと以前の自分用のメモを修正)。 |
| 2008.09.08 | v10.5.5、Xcode 3.1 用に解説更新。 |
管理人:神吉 秀典 E-mail: