XIBからViewを生成する4つの実装パターン

## はじめに

XIBファイルからカスタムViewを生成する方法はいくつかあります。
XIBファイルのFile's OwnerやViewのclassをどのように指定し、そこから生成したViewをどのようにclassに紐づけていくのかを以下の4つのパターンで記載していこうと思います。

| リンク | Files's Owner | View Class | ViewのIBOutlet紐付け | 利用法 |
| :-: | :-: | :-: | :-: | :-: |
| [1-1](1-1 UINib.inistantiate(nil).firstを直接つかう) | 指定なし (NSObject) | ProfileView | 指定なし | UINib.inistantiate(nil).first as! ProfileView |
| [1-2](1-2 UINibとViewを別クラスに) | 指定なし (NSObject) | _ProfileView | 指定なし | UINib.inistantiate(nil).first as! _ProfileViewをProfileViewにaddSubView  |
| [2-1](2-1 UINib.inistantiate(self).firstをaddSubview) | ProfileView | 指定なし (UIView) | 指定なし | UINib.inistantiate(self).first as! UIViewをProfileViewにaddSubView |
| [2-2](2-2 UINib.inistantiate(self)してviewをIBOutletで紐付ける) | ProfileView | 指定なし (UIView) | ProfileViewの\_view | UINib.inistantiate(self)し、IBOutletで紐付けた\_viewをProfileViewにaddSubView |

ここではView上にUILabelが載っているProfileViewというclassを実装します。

※ 1.Bundle.loadNibNamedではなく、UINib(nibName:bundle:)を使う前提にしています。
※ 2.view同じサイズでaddSubviewするコードを書く際に、長くなってしまうのでNSLayoutConstraintではなくAutoresizingMaskを利用してます。

## 1. Xibのviewをクラスに紐付ける

下図のように`New File`する際に、UITableViewCellなどのsubclassとXIBも生成する設定で追加を行うと、XIBファイル上のViewのclassは自動的にそのsubclassに指定されます。このとき、File's Ownerは指定されていない状態になっています。これらの状態をベースの話をすすめていこうと思います。

<img width="447" alt="スクリーンショット 2018-09-07 13.48.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/60325/04b72d15-0a6b-d4b2-9c72-09accf8cd2b6.png">

### 1-1 UINib.inistantiate(nil).firstを直接つかう

#### XIB

ViewのclassをProfileViewに指定します。

<img width="1167" alt="スクリーンショット 2018-08-29 13.52.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/60325/69b59703-bd85-2015-dc78-1f2039f66d3c.png">

UILabelはViewであるProfileViewに対してIBOutletで紐付けます。

<img width="1163" alt="スクリーンショット 2018-08-29 13.52.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/60325/9f72e692-3ca2-9835-b248-9b3f649f4f33.png">

#### 実装

XIBファイル上のViewがProfileViewと紐付いていてViewは一つしか存在しないため、`UINib(nibName: "ProfileView", bundle: nil).instantiate(withOwner: nil, options: nil).first`を呼び出すとProfileViewが返ってきます。
この場合、ProfileViewは既にインスンタンス化されているのでinitializerは利用できないため、クラス外から何かしらの値を受け取ってpropertyで保持する場合には、Optional（またはImplicitly Unwrapped Optional）でpropertyを定義する必要があります。
そのため、Viewをロードした後にpropertyに任意の値を代入をしてからProfileView返すstatic関数を定義する形になるかと思います。
また、ProfileViewはXIBからロードしてインスタンス化しているため、`init?(coder aDecoder: NSCoder)`と`awakeFromNib`が呼ばれます。

```swift
final class ProfileView: UIView {

    @IBOutlet weak var usernameLabel: UILabel!

    private(set) var user: User!

    static func make(user: User) -> ProfileView {
        let view = UINib(nibName: "ProfileView", bundle: nil)
            .instantiate(withOwner: nil, options: nil)
            .first as! ProfileView

        view.user = user

        return view
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func awakeFromNib() {
        super.awakeFromNib()
    }
}
```

### 1-2 UINibとViewを別クラスに

[1-1](1-1 UINib.inistantiate(nil).firstを直接つかう)とほぼ同じですが、XIBのViewと紐付ける\_ProfileViewとProfileViewを分けています。

#### XIB

Viewのclassを_ProfileViewに指定します。

<img width="1164" alt="スクリーンショット 2018-08-31 13.49.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/60325/5d94dee4-32f9-9dde-b495-72fcab3bec25.png">

UILabelはViewである_ProfileViewに対してIBOutletで紐付けます。

<img width="1163" alt="スクリーンショット 2018-08-31 13.49.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/60325/35d86e59-5129-7688-7fa0-181aed53ad94.png">

#### 実装

ProfileViewが実際に利用するclass、\_ProfileViewはレイアウトのclassです。
\_ProfileViewは`UINib(nibName: "ProfileView", bundle: nil).instantiate(withOwner: nil, options: nil).first`から取得し、ProfileViewで保持します。
ProfileViewでは、\_ProfileViewで持っているものと同じpropertyをcomputed propertyで返すようにしています。
また\_ProfileViewはProfileViewと同じサイズでaddSubviewしています。
利用するclassとレイアウトのclassを分けることで、利用する側のclassのinitializerが使えるようになります。
この場合、ロード時には\_ProfileViewの`init?(coder aDecoder: NSCoder)`と`awakeFromNib`が呼ばれます。

```swift
final class ProfileView: UIView {

    var usernameLabel: UILabel { return _view.usernameLabel }
    private let _view: _ProfileView

    let user: User

    init(user: User) {
        self._view = UINib(nibName: "ProfileView", bundle: nil)
            .instantiate(withOwner: nil, options: nil)
            .first as! _ProfileView

        self.user = user

        super.init(frame: .zero)

        _view.frame = bounds
        addSubview(_view)
        _view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    }
}

final class _ProfileView: UIView {
    @IBOutlet fileprivate weak var usernameLabel: UILabel!

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func awakeFromNib() {
        super.awakeFromNib()
    }
}
```

## 2. XibのFile's Ownerをクラスに紐付ける

[Xibのviewをクラスに紐付ける](## 1. Xibのviewをクラスに紐付ける)では、XIBファイルのViewにclassを紐付けていましたが、XIBのFile's Ownerにclassを紐付けます。

### 2-1 UINib.inistantiate(self).firstをaddSubview

#### XIB

File's OwnerのclassをProfileViewに指定します。

<img width="1166" alt="スクリーンショット 2018-08-31 13.56.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/60325/c4792c03-bac4-1374-63fe-12162caedf4f.png">

UILabelはFile's OwnerであるProfileViewに対してIBOutletで紐付けます。

<img width="1165" alt="スクリーンショット 2018-08-31 13.56.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/60325/348c0f22-d855-5b70-7402-aec615928e6e.png">

Viewのclassを未指定のままにします。

<img width="1162" alt="スクリーンショット 2018-08-31 13.56.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/60325/b50b3eba-63cc-f8fb-3453-2a6d7c976054.png">

#### 実装

XIBでFile's OwnerをProfileViewに指定しているので、`super.init`後にUINib.instantiate(withOwner: **self**, options: nil)として紐付けを行っています。
UILabelはFile's Owner（ProfileView）に対して紐付いているため、この時点でロードが行われます。
Viewのclassはコンテンツが載っているただのUIViewなので、`UINib.instantiate(self).first`から取得しProfileViewに対してaddSubviewします。
ProfileViewは自身のinitialize後に、XIBのFile's Ownerとして紐付けを行うので、ProfileViewのinitializerが使えます。
Viewとしてクラスに紐付いていないため、`init?(coder aDecoder: NSCoder)`と`awakeFromNib`は呼ばれません。

```swift
final class ProfileView: UIView {

    @IBOutlet private(set) weak var usernameLabel: UILabel!

    let user: User

    init(user: User) {
        self.user = user

        super.init(frame: .zero)

        let _view = UINib(nibName: "ProfileView", bundle: nil)
            .instantiate(withOwner: self, options: nil)
            .first as! UIView

        _view.frame = bounds
        addSubview(_view)
        _view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    }
}
```

### 2-2 UINib.inistantiate(self)してviewをIBOutletで紐付ける

#### XIB

File's OwnerのclassをProfileViewに指定します。

<img width="1165" alt="スクリーンショット 2018-08-31 14.01.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/60325/75d70d8d-6aa0-0277-c1af-c629f29507e2.png">

UILabelはFile's OwnerであるProfileViewのusernameLabelに、Viewは_viewに対してIBOutletで紐付けます。

<img width="1164" alt="スクリーンショット 2018-08-31 14.01.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/60325/608aa404-b73e-8f8e-f5bb-34f2bf270952.png">

Viewのclassを未指定のままにします。

<img width="1164" alt="スクリーンショット 2018-08-31 14.02.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/60325/7da4af03-2aeb-8e51-00aa-5a0020b7faad.png">

#### 実装

XibのViewはFile's Owner（ProfileView）の_viewに紐付いているため、`UINib(nibName: "ProfileView", bundle: nil).instantiate(withOwner: self, options: nil)`をした際にロードされます。
よって、[2-1](2-1 UINib.inistantiate(self).firstをaddSubview)のように、`UINib.instantiate(self)`**.first**でviewを取得する必要はなくなります。
このviewの紐付き方は、`New File`からUIViewControllerとXIBを同時に生成した場合に自動生成されるXIBの設定と同等のものになります。
また、この場合もViewはProfileViewではなくてコンテンツが載っているただのUIViewなので、didSetでProfileViewにaddSubviewしています。
Viewとしてクラスに紐付いていないため、`init?(coder aDecoder: NSCoder)`と`awakeFromNib`は呼ばれません。

```swift
final class ProfileView: UIView {

    @IBOutlet private(set) var _view: UIView! {
        didSet {
            _view.frame = bounds
            addSubview(_view)
            _view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        }
    }

    @IBOutlet private(set) weak var usernameLabel: UILabel!

    let user: User

    init(user: User) {
        self.user = user

        super.init(frame: .zero)

        UINib(nibName: "ProfileView", bundle: nil)
            .instantiate(withOwner: self, options: nil)
    }
}
```

また以下の実装のようにXIBからロードする用のView classを作成することで、XIBを利用する各classごとに_viewを定義する必要がなくなります。

```swift
final class ProfileView: XibView {

    @IBOutlet weak var usernameLabel: UILabel!

    let user: User

    init(user: User) {
        self.user = user

        super.init(frame: .zero)
    }
}

class XibView: UIView {

    class var nibName: String {
        return String(describing: self)
    }

    @IBOutlet private var _view: UIView! {
        didSet {
            _view.frame = bounds
            addSubview(_view)
            _view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        UINib(nibName: type(of: self).nibName, bundle: nil)
            .instantiate(withOwner: self, options: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
        UINib(nibName: type(of: self).nibName, bundle: nil)
            .instantiate(withOwner: self, options: nil)
    }
}
```

## 個人的な感想

[Xibのviewをクラスに紐付ける](## 1. Xibのviewをクラスに紐付ける)場合は、自身がXIBのViewになっているあるいは明確にレイアウトとしてclassが分かれているのでコード上からも追いやすいなと思っています。
[XibのFile's Ownerをクラスに紐付ける](## 2. XibのFile's Ownerをクラスに紐付ける)場合は、XIBのviewは別viewになっていますが、紐付いた明示的な別クラスがあるわけではなくただのUIViewなので、構造を理解するに要する時間が[Xibのviewをクラスに紐付ける](## 1. Xibのviewをクラスに紐付ける)場合よりもかかってしまうと思っています。
ただ慣れれば、[2-2 UINib.inistantiate(self)してviewをIBOutletで紐付ける](### 2-2 UINib.inistantiate(self)してviewをIBOutletで紐付ける)が使いやすいと思います。