この記事は古いものです。Auto Layoutの対応を含めると以下の記事の対応が望ましいと思います。(2014/1/25)

また下記の記事内容のようにxibからロードしてCustom Viewをインスタンス化するより、上記記事のようにCustom Viewクラスの初期化メソッドでxibをロードしSubviewとしてaddする方が良いと思います。理由は他のxibやstoryboardでCustom Viewを配置できるようになるからです。この記事の方法ではプログラムからしかCustom Viewを生成できないと思います。Auto Layoutを駆使できるようになると、プログラムからViewを配置する機会は減り、Interface BuilderでCustomViewも含めて配置したくなると思います。

以下、古い記事の内容。

 


 

UIViewControllerと関係なくViewのSubclassとひもづけたい時があります。再利用したいViewがまさにそうでしょう。今回はこの方法を説明します。

※ このネタ、意外にググってもやり方がみつからず、だいぶ時間つぶしました。

Viewのxibを作成する

まずはViewのxibを作ります。ここではSTCustomView.xibとします。

f:id:eimei23:20130211115615p:plain

View上にLabelやButtonを配置。

f:id:eimei23:20130211115736p:plain

このViewはSubviewとして使うのでステータスバーは不要です。またサイズも端末サイズ固定ではなく可変です。実際、xib上はこのままでも問題ないのですが、一応調整しておきましょう。ViewのAttribute Inspectorを開いて、SizeとStatus BarをNoneにします。

f:id:eimei23:20130211120913p:plain

サイズ変更して、こんな感じに。独立したViewっぽくなりました。この方が人が見てもわかりやすいですね。

f:id:eimei23:20130211121028p:plain

LabelやButtonなどのAutoresizingMaskを適切に設定。

f:id:eimei23:20130211121525p:plain

f:id:eimei23:20130211121536p:plain

f:id:eimei23:20130211130718p:plain

Viewのsubclassを作る

次にxibとひもづくViewのSubclassを作りましょう。xibに合わせてSTCustomViewという名前で作ります。

そしてSubviewとひもづけるメンバー変数をIBOutletで定義します。

@implementation STCustomView {
    IBOutlet UILabel *_label;
    IBOutlet UITextField *_textField;
    IBOutlet UIButton *_button;
}

今回はこれらをクラス外部から参照しないので、STCustomView.mに上記のように定義しています。もちろんpropertyとして定義して外部から参照するようにもできます。

次にxibからViewがロードされて、IBOutletとひもづいたあとの処理をawakeFromNibに書きます。

- (void)awakeFromNib
{
    _label.textAlignment = NSTextAlignmentCenter;
    _textField.placeholder = @"Please push.";
    [_button addTarget:self action:@selector(buttonDidTap) forControlEvents:UIControlEventTouchUpInside];
}

自分はxib側でプロパティの設定をしたり、ボタンイベントをひもづけたりするのが嫌いなので、ここに書いてます・・・xib側はなるべくレイアウト管理だけにしたい。プログラムとxibどっちでやっているかわからなくなるのが嫌です。

STCustomView.xibとSTCustomViewクラスのひもづけ

次にxibとクラスのひもづけを行います。STCustomView.xibを開いてViewを選択し、Identity Inspectorを選択。ここのCustom Classの項目をUIViewからSTCustomViewに変更します。

f:id:eimei23:20130211123120p:plain

次にViewのConnections Inspector選択すると、STCustomViewでIBOutlet定義した変数が表示されているので、これらをView上のSubviewとひもづけます。

f:id:eimei23:20130211124151p:plain

※ このConnections Inspectorの存在に気づかず時間つぶしました・・・

注意すべきなのはFile’s  OwnerのIdentity InspectorのCustom Classの項目をSTCustomViewにして、ViewControllerのときと同じようにFile’s OwnerからSubviewをひもづけてはいけないことです。

f:id:eimei23:20130211150317p:plain

これをやってしまうと以下のエラーが出ます。

Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSObject 0x6858780> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key _button.'

※ これやってエラーと格闘して時間つぶしました・・・

ここはViewControllerの時とは異なるので注意が必要です。 File’s OwnerのCustom Classは、NSObjectのままにしておきましょう。ここだけ変えてもSubviewとひもづけしなければ、エラーになりませんが念のため。

STCustomViewクラスを生成する

プログラムからSTCustomViewクラスを生成します。UINibクラスを使って行うのですが、この処理は他のカスタムViewでも使うかもしれないので、UIViewのカテゴリメソッドにしておくと良いです。

+ (id)loadFromNib
{
    NSString *nibName = NSStringFromClass([self class]);
    UINib *nib = [UINib nibWithNibName:nibName bundle:nil];
    return [[nib instantiateWithOwner:nil options:nil] objectAtIndex:0];
}

NSStringFromClassでloadFromNibメソッドを実行しているクラス名を得て、それを元にUINib#nibWithNibName:bundleでxibをロード。UINib#instantiateWithOwnerでインスタンス化します。このメソッドは配列で返ってきます。先頭要素にSTCustomViewが入っているので、それを取り出して返します。

ちなみにiPhoneとiPadで別のxibファイルをロードしたいときは、以下のようなカテゴリメソッドを作っておくと良いでしょう。

+ (id)loadFromIdiomNib
{
    NSString *nibName = NSStringFromClass([self class]);
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        nibName = [nibName stringByAppendingString:@"_iPhone"];
    } else {
        nibName = [nibName stringByAppendingString:@"_iPad"];
    }
    UINib *nib = [UINib nibWithNibName:nibName bundle:nil];
    return [[nib instantiateWithOwner:nil options:nil] objectAtIndex:0];
}

クラス名 + _(アンダースコア) + iPhone.xibか、クラス名 + _(アンダースコア) + iPad.xibか、適したものを読むようになります。

今回のサンプルはgithubにcommitしてあります。

https://github.com/stack3/STLoadViewFromXibSample

ちなみに今回のようにViewとxibをひもづける場合、そのファイル名がViewControllerとだぶらないように注意が必要です。この件は以下の記事を参考にしてください。

http://blogios.stack3.net/archives/190