|
|
9. 一覧表示の追加
一覧表示の機能を追加する
前回までで Learn ObjC Lite で作った「intro」コマンドラインプログラムを GUI プログラム (アプリケーション) に作り替える作業はほぼ終了しました。次に気になることは、このアプリケーションにどのようにしてアイコンを付けるのかというような色々な設定の方法と、アプリケーションの日本語化だと思います。しかしその前に今のプリケーションにはどうしても不便な点が1つあります。それはどのような人達が登録されているのかを知る機能がまったく付いていないということです。Greeting (挨拶) タブの名前欄にうろ覚えの名前を入力して自己紹介文が表示されるか、あるいは「登録はありません」と表示されるのを見て思いだすしか方法がありません。これはサンプルプログラムとは言え、あまりにも使いにくいと思います。そこで今回は「各種設定」や「日本語化」の前に登録者の一覧を表示する機能を追加したいと思います。
今回は NSTableView という GUI パーツの中では比較的に高度なものを使います。そして前回登場したデリゲートと同じように実際の作業を他のオブジェクトに頼る dataSource (データソース) という仕組みも使います。
また私はすでに Xcode 3.1 にアップデートしました。今後スクリーンショット (画像) は 3.0 の場合と違う可能性があります。しかしたぶん分かると思います。なお Xcode 3.1は
ADC の一番上にある「ADC member Site」の「Download」からダウンロードできます。あるいは同じ ADC の画面左にある iPhone SDK をダウンロードしてインストールしても Xcode 3.1 がインストールされます。どちらの場合も登録が必要ですが無料でダウンロードできます。
タブの追加
まずはじめに「一覧表示」用にタブを追加します。Introducer プロジェクトを起動してプロジェクトウィンドウで「MainMenu.nib (English) をダブルクリックして Interface Builder を起動します。そして次の図のように Tab View 全体を選択します。
|
|
インスペクタパネルのタイトルが Tab View xxxx になっていることを必ず確認して下さい。そしてまず一番左の Attributes ペインに移動します
Default Tab で「Selected Tab View Item (Greeting)」を選びます。これを選んでおくと interface Builder での作業がどのタブで終ってもアプリケーション起動時には Greeting タブから始まるようになります
Tabs を 4 に変更します。後はデフォルトのままで結構です。
|
次の図のように新しいタブが追加されます
追加されたタブを選択します
|
|
インスペクタパネルのタイトルが Tab View Item Attributes になっていることを確認して下さい
Label を「Enrollees」(登録者たち) にします
Identifier を 4 にします
このアプリケーションではこの Identifier で指定された文字列「4」を Enrollees タブが選ばれた場合の識別子とします。Enrollees タブが選ばれた場合にはテーブルビューを最新のデータで描画します
2008年7月19日時点では
「このアプリケーションではタブビューの Identifier は使いませんが、一応その他のタブに合せて 4 にしておきました」と書きましたが間違いでした。当初 Label の Enrollees という文字列をこのタブが選ばれた場合の識別子に使う予定でしたが、よく考えれば日本語にローカライズした場合にこの Enrollees という文字列も変わってしまいます。そこで Identifier の「4」を識別子とすることにいたしました。
|
テーブルビューの追加
|
|
ライブラリパネルで Cocoa フォルダ → Views & Cells フォルダから Table View を選んで新しく作った Enrollees タブにドラッグします
|
テーブルビューの大きさはガイドラインぎりぎり一杯まで広げます
次に一度ウィンドウのタイトルバーあたりをクリックしてインスペクタウィンドウで現在選択されているのが Window であることを確認します。そして再度テーブルビューの真ん中あたりをクリックします。インスペクタウィンドウのタイトルが Scroll View xxxx に変われば OK です。そして次のように設定します
|
|
一番左の Attributes タブを選択します。インスペクタウィンドウのタイトルが Scroll View Attributes になっていることを確認します
Show Horizontal Scroller のチェックを外します。これでテーブルの回りに表示されるスクロールバーは縦のみになります。後の設定はデフォルトのままで結構です
ここではテーブルビューをクリックしたにもかかわらずスクロールビューが選択されました。テーブルビューはスクロールビューの中に含まれています。そしてさらにテーブルビューの中にはテーブルカラムというオブジェクトが含まれています。このようにテーブルビューは複数の階層で構成されています。今現在何が選択されているか (何が作業の対象になっているか) を常にインスペクタウィンドウのタイトルバーで確認するようにします。
|
次に再度テーブルビューの真ん中あたりをクリックします。インスペクタウィンドウのタイトルが Table View xxxx に変わるのを確認できたら次のように設定します
|
|
一番左の Attributes タブを選択します。インスペクタウィンドウのタイトルが Table View Attributes になっていることを確認します
Columns を 1 に設定します
Grid Lines の Horizontal にチェックを入れます。あとはデフォルトのままで結構です
|
テーブルビューは次のようになります
更にもう一度テーブルビューの真ん中あたりをクリックします。インスペクタウィンドウのタイトルが Table Column xxxx に変わります。そして次のように設定します。
|
|
一番左の Attributes タブを選択します。インスペクタウィンドウのタイトルが Table Column Attributes になっていることを確認します
Title を Enrollee に設定します
Identifier を name に設定します。この Identifier で設定した文字列はあとでこのテーブルカラムを識別するために使います。
その他はデフォルトのままで結構です
|
最終的にテーブルビューは次のようになります
ちなみに Table Column が選択された状態でインスペクタウィンドウの Size ペインを見ると次のようになっています。
dataSource を接続する
テーブルビューは実際にそのセル (各行の各カラム) に表示するデータは保持していません。その替わりに detaSource という参照型のインスタンス変数に接続されたオブジェクトにデータを保持させてそれを参照して各セルのデータを埋めていきます。Cocoa は GUI パーツ (GUIオブジェクト) が実際に行う作業・振る舞いをできるだけ外部のオブジェクトに委ねるように設計されています。例えばボタンをクリックした場合に実際に行う作業はコントローラーオブジェクトに記述しました。タブビューを切り替えた時やアプリケーションを終了する時には delegate という参照型インスタンス変数に接続されたオブジェクトにその場合の振る舞いや動作を記述しました。出来るだけ GUI オブジェクトをコンパクトにまとめて、しかも出来るだけサブクラスなどを作らない方向で Cocoa 全体が設計されているみたいです。
これに対してその GUI パーツが行うすべての動作や振る舞いをその GUI オブジェクトの中に実装しようという設計思想のオブジェクト指向言語もあります。この場合はボタンであればボタン1つ1つがクリックされた場合の動作が違うものなので、1つ1つのボタンに対してボタンのサブクラスを1つ1つ作りそこにクリックされた場合の動作 (メソッド) を記述します。どちらが取っ付き易いかというと後者になると思います。「そのボタンが行う作業はそのボタンに記述する」わけですから分かりやすいと思います。しかしアプリケーションの記述がどんどん進んでいった場合にどちらがコードの流れを理解しやすいかというとたぶん前者だと思います。ただしこれはあくまでも私の主観です。
dataSource の接続
Interface Builder 上でテーブルビューを選択します。うまく選択できない場合は、まず Window を選択して次に Scroll View を選択します。そして Table View を選択するという作業を繰り返します。今なにが選択されているかを常にインスペクタウィンドウのタイトルバーで確認して行ってください。Table View が選択できましたら Table View から MainMenu.nib ウィンドウの中の Controller オブジェクトまで右クリックもしくは control + 左クリックしながら接続線を引きます。下図のように Controller オブジェクトが四角で囲まれたことを確認してからマウスボタンを離します
どのインスタンス変数 (Outlet) に Controller オブジェクトを接続するのかを選ぶウィンドウが表示されますのでそこで dataSource を選びます
以上で Interface Builder での作業は終わりです。「File」メニューから「Save」を選ぶか ⌘ + S で作業を保存します。Interface Builder は終了して頂いて結構です。
dataSource メソッドを実装する
データソース・メソッドの実装をする前に頭の中を整理するためにこれまでに出てきた「既存のクラスに新たなメソッドを追加する方法」を簡単に整理したいと思います。なお Controller オブジェクトは Tab View と NSApplication のデリゲートでありなおかつ Table View のデータソースになりました。このように1つのオブジェクトが複数のオブジェクトのデリゲートやデータソースを兼ねることはまったく問題ありません。
既存のオブジェクトにメソッドを追加する方法
1. プロトコル
インターフェース部で @interface クラス名 : スーパークラス名 <プロトコル名> と記述することによってそのプロトコルで定義されているメソッドのすべてをそのクラスに宣言をしたことになります。プロトコルで定義されているメソッドを @interface 部で新たに宣言する必要はありません。ようるすにプロトコロルはメソッド宣言の役割を果たしているのです。逆に @implementation 部 (実装部) ではプロトコルで定義されているメソッドのすべてを実装しなければなりません。当然、実装の内容は自由に記述できます。プロトコルについては
Learn ObjC 第6回 ファイル入出力 の中の「プロトコルの採用」でもう少し詳しい説明をしております。
なお今まではメソッド宣言やメソッド定義という呼び方を使ってきましたが、最近のADCのガイドでははっきりとプロトタイプと呼んでいるものが出て来ています。ようるすに @interface 部でのメソッド宣言は C言語の関数のプロトタイプと同じ役割だと考えてもらって結構です。またインスタンス変数もADCの文書によってはメンバ変数と呼んでいるものが出て来ています。オブジェクトのことをあまり難しく考えないで構造体などのデータ構造にそれ専用の関数が一緒になったものという捉え方でOKだと思います。
2. デリゲート
あるクラスにデリゲート機能を搭載するのはかなり高度な技術が必要となります。したがってデリゲートを自作クラスに搭載することはまずないと思って頂いて結構です。実際にプロトタイプとデータソースではそれぞれの参照先にメソッド実体がある形になりますが、デリゲートではそういう形は見えてきません。なおデリゲート先として接続するオブジェクトには特に制限はありません。デリゲート先には必要となるデリゲートメソッドの宣言 (プロトタイプ) と実装だけを行えばよくプロトタイプのようにすべてのメソッドを実装する必要はありません。
3. データソース
データソースは NSObject で非形式プロトコルとして定義されているメソッド宣言群 (メソッド・プロトタイプ) です。したがってすべてのオブジェクトがデータソースの役割を果たすことができます。ここで非形式プロトタイプと呼びましたが、一応そういう呼び方をしているだけでプロトタイプとはまったく趣きの違う何の関係もないものだと思って頂いて結構かと思います。データソースメソッドは必要なものだけを実装すればよく (この点はデリゲートに似ています) 、メソッド宣言 (プロトタイプ) は省略することができます (この点で言えばプロトタイプに似ています。またメソッド宣言は記述しても構いません)。どちらかと言えばメソッド宣言をするほうが正式みたいですが、今回のサンプルプログラムでは省略させて頂きました。
では実際のソースコードを見ていきます。
ITController.h
ここでの変更は32行目に NSArray *names; という NSArray 型のポインター names をインスタンス変数に加えている点だけです。あとはすべて前回のままです。この names 変数にテーブルビューで表示されるデータを格納します。
ITController.m
ITController.m ではデリゲート・メソッドとデータソース・メソッドより前の部分に変更はありません。
tabView: willSelectTabViewItem: メソッド 91行目〜109行目
93行目 メソッド内で使うローカル変数 tmpArray を宣言しています
94行目 tabViewItem は上記メソッドの第2引数でタブビューを切り替えた際に選択されたタブを表しています。その tabViewItem に identifier メソッドを送るとインスペクタウィンドウで設定した Identifier の値が返ってきます。isEqualToString: メソッドで返ってきた値が文字列としての 4 と同じであるかどうかを判定しています。もしこの if 分が YES であれば96行目に進みます
96行目 NSArray クラスに arrayWithArray: クラスメソッドを送って引数と同じ内容の配列で tmpArray を初期化しています。arrayWithArray: メソッドは一時的オブジェクトを作るメソッドですがこのような一時的オブジェクトをつくるメソッドを Objective-C 2.0 ではコンビニエンスコンストラクタと呼ぶようになったみたいです。[model dictionary] では ITModel が保持している辞書データを取り出してそれに対して allKeys を呼び出すと辞書データのキー部分だけの配列が返ってきます
97行目 tmpArray 配列データを昇順(あいうえお順、もしくはABC順)にソートしています。引数の @selector(compare:) がソートの条件 (この場合では昇順ソート) を指定しています。
99行目〜107行目 前回とまったく同じコードです。「Enrollees」タブが選ばれた場合にだけテーブルビュー用のデータの初期化を行い。その他のタブが選ばれた場合は前回と同じように各タブ内に表示される文字列をまとめて変更しています。
applicationWillTerminate: メソッド 111行目〜114行目
変更はありません
ここからはデータソース・メソッドになります
numberOfRowsInTableView: メソッド 120行目〜123行目
122行目では names に count メソッドを送って配列の要素数を得て、それを return で返しています。この値が結局テーブルビューの縦の数 (行数) になります。
tableView: objectValueForTableColumn: row: メソッド 125行目〜136行目
127行目 id 型の identifier という変数を宣言しています。
129行目 tableColumn は上記メソッドの第2匹数です。この変数に対して Identifier メソッドを送ると Table Column の Identifier で設定したオブジェクト(文字列)が返されます。それを変数 identifier に代入しています。
131行目〜133行目 Identifier 変数に代入された文字列が「name」という文字列ならば names 配列に objectAtIndex: メソッドが送られます row 変数は上記メソッドの第3引数でテーブルの何行目かを表す整数値です。結果的に name カラム (列) の row 行目の値が返されることになります。
135行目 return nil; この記述は必要なさそうですが書いておかなければコンパイラが警告を出す場合があります。
ビルドと実行
以上で「一覧表示機能」を追加する作業はすべて終了しました。「ビルド」メニューから「ビルド」を選んでコンパイルし、「実行」メニューから「実行」を選択してアプリケーションを実行します。あるいはプロジェクトウィンドウのツールバーにある「ビルドして進行」をクリックするとコンパイルから実行までを通して行ってくれます。ただしたまにコンパイルは「無事に完了しました」と表示されるにも関わらずアプリケーションが起動しない場合があります。これは私も初めての経験です。もしそういう状態になった場合は commnad + option + esc で強制終了画面を呼び出して「Introducer」ではなくて「Xcode」のほうを強制終了してください (Introducer はそれに伴って自動的に終了されます)。そして「ビルド」→「すべてのターゲットをクリーニング」を選んでクリーニングをしてから再度ビルドと実行をしてみてください。これでたぶん問題なくアプリケーションは実行されると思います。アプリケーション起動後「Enrollees」タブを選ぶと次のように表示されることでしょう。
なお先ほどのコンパイルは正常に終るのにアプリケーションは起動されないという現象には次のような原因が考えられると思います
1. コード記述のどこかにマズイ点がある
2. Xcode 3.1 のバグでメモリーリークが起こっている
3. 単なる設定ミス
次回はこのアプリケーションの設定を行います。それまではしばらくこのまま様子をみてみようと思っています。
お疲れさまでした。これで Cocoa GUI App 第9回は終ります。
|
|
|