UIViewをNibから初期化する


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

サンプルコードが[こちら](https://github.com/gonsee/NibViewSample)にあります。

![スクリーンショット 2014-03-18 0.31.28.png](https://qiita-image-store.s3.amazonaws.com/0/32084/66a81223-8802-b815-88ee-68f4d94eb04f.png "スクリーンショット 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をソースコード側とつなぐ

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

```objc
// CustomView1.h

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

```objc
// 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と同様につなぐ

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

```objc
// CustomView2.h

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

```objc
// 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:`が呼ばれるので、必要な処理は共通の初期化メソッドの中に書きます。