Help us understand the problem. What is going on with this article?

UIViewをNibから初期化する

More than 5 years have passed since last update.

再利用可能なカスタムビュークラスを、Nib(xibファイル)を使って作る方法を考えます。例としてViewの上にLabelとButtonがひとつずつ乗っているカスタムビューを作ります。

サンプルコードがこちらにあります。

スクリーンショット 2014-03-18 0.31.28.png

ルートのViewにカスタムクラスを指定する方法

  • CustomView1.h, CustomView1.m, CustomView1.xibを作る
  • Interface Builderで図のようにView, Label, Buttonを配置する
  • ルートのViewのClassをCustomView1にする
  • File's Ownerはなし
  • LabelのoutletとButtonのactionをソースコード側とつなぐ

ソースコードは以下のように実装します。

// CustomView1.h

@interface CustomView1 : UIView
@property (weak, nonatomic) IBOutlet UILabel *label;
+ (instancetype)view;
@end
// CustomView1.m

#import "CustomView1.h"

@implementation CustomView1

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {

    }
    return self;
}

- (void)awakeFromNib
{
    [super awakeFromNib];

    // 初期化
    self.label.text = NSStringFromClass([self class]);
}

+ (instancetype)view
{
    NSString *className = NSStringFromClass([self class]);
    return [[[NSBundle mainBundle] loadNibNamed:className owner:nil options:0] firstObject];
}

- (IBAction)buttonTapped:(id)sender
{
    NSLog(@"tapped.");
}

使う側のクラスでは以下のようにします。

CustomView1 *cv1 = [CustomView1 view];
[self.view addSubview:cv1];

-[NSBundle loadNibNamed:owner:options]を呼ぶとinitWithCoder:awakeFromNibが呼ばれます。初期化の処理はoutlet等がインスタンス化された後に呼ばれるawakeFromNibの中でやるのが良いでしょう。

[[CustomView1 alloc] init]のように使いたい場合は指定イニシャライザのinitWithFrame:をオーバーライドする必要があります。

- (id)initWithFrame:(CGRect)frame
{
    self = [[self class] view];
    return self;
}

この方法はNibから直接欲しいカスタムビュークラスをインスタンス化していてわかりやすいですが、カスタムビューを使う側はコードで書かなければならず、他のNib内で使う事ができません。

File's Ownerにカスタムクラスを指定する方法

  • CustomView2.h, CustomView2.m, CustomView2.xibを作る
  • CustomView1と同様にInterface BuilderでView, Label, Buttonを配置する
  • File's OwnerにCustomView2を指定する
  • IB上のViewはCustomView2のcontentViewというoutletにつなぐ
  • Label, ButtonはCustomView1と同様につなぐ

ソースコードは以下のように実装します。

// CustomView2.h

@interface CustomView2 : UIView
@property (weak, nonatomic) IBOutlet UIView *contentView;
@property (weak, nonatomic) IBOutlet UILabel *label;
@end
// CustomView2.m

#import "CustomView2.h"

@implementation CustomView2

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self p_commonInit];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self p_commonInit];
    }
    return self;
}

- (void)p_commonInit
{
    NSString *className = NSStringFromClass([self class]);
    [[NSBundle mainBundle] loadNibNamed:className owner:self options:0];

    self.contentView.frame = self.bounds;
    [self addSubview:self.contentView];

    self.label.text = className;
}

- (IBAction)buttonTapped:(id)sender
{
    NSLog(@"tapped.");
}

Nibからインスタンス化したViewをselfにaddSubviewしているので、よけいなviewの階層がひとつ増えてしまいます。

しかし使う側は[[CustomView alloc] init]のようにコードから使う事もできますし、Interface Builderで配置したViewのクラスにCustomeView2を指定して利用する事もできます。前者の場合initWithFrame:が、後者の場合initWithCoder:が呼ばれるので、必要な処理は共通の初期化メソッドの中に書きます。

gonsee
カレンダーシェアアプリ「TimeTree」のiOSアプリを開発しています。個人では「陣痛時計」、「ごみの日アラーム」というアプリをリリースしています。Perfumeエバンジェリスト。
https://simplebeep.net/iphone-apps
jubileeworks
「毎日に、新しい"なくてはならない"を創る」を経営理念に掲げ、共有カレンダーサービス TimeTree の開発・運営をしています。
https://timetreeapp.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away