2012年8月24日金曜日

InterfaceBuilderを用いて再利用可能なコンポーネントを作る

「こういう風にやればできます」系の画像付き説明は多いんだけど、冗長なやり方してる場所が多い気がする。そもそもObjective-Cの基礎的な言語知識がある程度ないと、やり方を知れても、理解することはできないかもな感じですが。

xibとnibの違い

InterfaceBuilderで生成したレイアウトファイルがxib。この中身はxmlです。

以前書いたけど、xibとは単にグラフィカルレイアウトが記述されているファイルではなくて、実行時にデシリアライズされるオブジェクト群が記述されています。

コンパイル時にオブジェクトがシリアライズされたnibに変換されるのです。

nibのデシリアライズのやり方

nibを使ってViewだけを取り出したいという場合、無名ViewControllerを使うやり方と、Bundleから直接取り出すやり方があります。

1.無名ViewControllerを使う方法

ViewControllerのinitWithNibname:bundle:メソッドを用いると、UIViewControllerクラスがFile's Ownerに指定されているnibを生成することができます。

File's OwnerがUIViewControllerでないnibをこの方法で生成しようとすると、当たり前だけど失敗してエラーで落ちます。

この方法で生成した無名ViewControllerは、viewをマネジメントするためのカスタムコードが記述されていないので何もしません。releaseを忘れると、viewリソースがリークしますので、さっさとreleaseするのが基本です。

何らかのマネジメント(例えば画面回転時にviewの表示をオリエンテーションに対応させたいなど)が必要ならば、ViewControllerを記述して親ViewControllerをコンテナ化させる必要があります。

2.Bundleを使う方法

NSBundleクラスのmainBundleでアプリケーションのメインバンドルを取得できます。

initWithNibname:bundle:でも使うバンドルとは、実行可能ファイルや画像などの各種リソースをパッケージングする仕組みです。メインバンドルとは、アプリケーションの実行ファイルや設定ファイルが格納されたバンドルのことです。

無名ViewControllerなど使わなくても、Bundleから直接nibを生成することができます。そのためのメソッドがloadNibNamed:owner:options:です。

この方法ではownerに指定した任意のオブジェクトをFile's Ownerに指定できるので、ViewControllerがFile's Ownerではないnibも呼び出せます。

返り値はnibのトップレベルオブジェクトが保持されたNSArrayなので、lastObjectを使うとルートビューが取得できます。

3.nibからオブジェクトを呼び出すときの注意

nibを使った場合、使用される初期化メソッドがinitWithCoder:になります。これはオブジェクト永続化のために定められた、NSCodingプロトコルで宣言されているデシリアライズに使うメソッドです。もし追加の初期化処理が必要な場合はここに書きます。

nibから生成されたすべてのオブジェクトは、生成処理が行われたにawakeFromNibがコールされます。時々「InterfaceBuilderを使うときには、awakeFromNibで初期化を行う」という記述を見ますが、これはオブジェクト自身の初期化に使うメソッドではありません

オブジェクトのデシリアライズ順序は保証されませんので、nibからロードされた特定のオブジェクト間を関連付けるような追加の初期化作業が必要な場合に、使うメソッドです。

例えば複数種類の外観を持つnibを、ユーザーが動的に選択するようなケースに使用するメソッドです。

File's Ownerで「動的」に

「動的」はObjective-Cの鍵です。

「静的」はコンパイル時点ですべてが決定されていることを意味します。

無名ViewControllerを使うコードをやたら見ますが、必要なデリゲート処理やアウトレットの保持が、xib内部のオブジェクトで完結しているケースなのでしょう。設計の時点で「静的」に閉じているケースではそれでいいのかもしれません。

「動的」は実行時に決定されることを意味します。

File's Ownerを実行時に関連付けることで、実行時にメソッド実装を切り替えたり、あるいは呼び出すnibを実行時に選択することで、初心者向けの簡単操作バージョンと上級者向けのボタン大量バージョンを、同一のViewControllerで提供できるのです。


例)エレガントにカスタムキーボードを呼び出す

UITextFieldやUITextViewは、カスタムクラスを作り、inputViewをオーバーライドすると、カスタムキーボードを使用することができます。

カスタムキーボードのレイアウトをInterfaceBuilderでやる場合、その処理を誰にデリゲーションすればいいのでしょうか?わざわざViewControllerを用意する必要はありません。

UITextFieldなどのカスタムクラスをFile's Ownerに指定して、カスタムキーボードに必要なメソッドを実装、UIのイベントと紐付けます。

あとはinputViewで、メインバンドルからloadNibNamed:owner:options:を呼び出し、オーナーを自分自身に関連付ければいいのです。

後日、ランドスケープでもカスタムキーボードが使えるようにしたいと仕様変更が入りました。オートサイジングでは見栄えが悪いのでレイアウトを変えたいです。

どうすればいいのか分かりますよね?ランドスケープ用にxibを定義して、画面の傾きに応じてどちらのnibを呼び出すのか動的に変更するようにすればいいのです。

0 件のコメント:

コメントを投稿