UIView系のサブクラスを作る際に、storyboardでの生成も考える時の注意点

# はじめに

以前、`UITextView`を継承して`Placeholder`付きの`UITextView`を作ったときに、

1. コードでサブクラスを生成・`Placeholder`のセットを行った場合
2. storyboardでサブクラス生成・`Placeholder`のセットを行った場合

とで初期化直後の状態が異なっているときがありました。

サブクラスをstoryboardで生成した場合のプロパティの初期化のタイミングを理解していなかったことが原因だったためそのことについてメモ。


# 初期化のタイミング

検証用にUIViewを継承したサブクラスを作りました。
UIViewにUILabelをサブビューとして持たせて、`@IBInspectable`で`title`をセットできるようにしただけのものです。

```CustomView.swift

@IBDesignable class CustomView: UIView {
    
    let titleLabel = UILabel()
    
    // -----------------------------------------------------------------

    override var frame: CGRect {
        didSet {
            print("2. frame did set \(frame).")
        }
    }

    override var backgroundColor: UIColor? {
        didSet {
            print("3. backgroundColor did set \(backgroundColor).")
        }
    }
    
    // -----------------------------------------------------------------
    
    override init(frame: CGRect) {
        print("1. Will super.init(frame: frame)")
        super.init(frame: frame)
        print("4. Did super.init(frame: frame)")
        config()
    }
    
    required init?(coder aDecoder: NSCoder) {
        print("1. Will super.init(coder: aDecoder)")
        super.init(coder: aDecoder)
        print("4. Did super.init(coder: aDecoder)")
        config()
    }
    
    func config() {
        titleLabel.frame = CGRect(origin: .zero, size: CGSize(width: frame.width, height: frame.height / 2))
        titleLabel.textColor = UIColor.red
        titleLabel.textAlignment = .center
        titleLabel.backgroundColor = UIColor.gray
        self.addSubview(titleLabel)
        print("5. Add subview titleLabel.")
    }
    
    // -----------------------------------------------------------------
    
    @IBInspectable var title: String? {
        didSet {
            titleLabel.text = title
            print("6. title did set \(title).")
        }
    }

}

```

以下を実行します。`backgroundColor`と`title`をセットします。これをコードとstoryboardでやったときの違いを見ます。

```ViewController.swift

    @IBOutlet weak var customView: CustomView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let customView2 = CustomView(frame: CGRect(x: 67, y: 250, width: 240, height: 128))
        customView2.backgroundColor = UIColor.init(red: 1, green: 0.4, blue: 0, alpha: 1)
        customView2.title = "Markdown"
        view.addSubview(customView2)
        
    }

```

<p align="center"><img width="400" alt="screenshot.png" src="https://qiita-image-store.s3.amazonaws.com/0/129900/fc8f2488-7f52-0f4e-b78a-b22398392d51.png"></p>


出力結果はこのようになります。

```

// storyboard
1. Will super.init(coder: aDecoder)
2. frame did set (67.0, 92.0, 240.0, 128.0)
3. backgroundColor did set Optional(UIExtendedSRGBColorSpace 0 0.478431 1 1)
4. Did super.init(coder: aDecoder)
5. Add subview titleLabel.
6. title did set Optional("Qiita").  // subclassのプロパティ

// code
1. Will super.init(frame: frame)
2. frame did set (67.0, 250.0, 240.0, 128.0)
4. Did super.init(frame: frame)
5. Add subview titleLabel.
3. backgroundColor did set Optional(UIExtendedSRGBColorSpace 1 0.4 0 1)
6. title did set Optional("Markdown").   // subclassのプロパティ

```

<p align="center"><img width="300" alt="screenshot.png" src="https://qiita-image-store.s3.amazonaws.com/0/129900/e7762c56-f654-3a8d-4720-b486da15cff5.png"></p>


この例では見た目に変化はないですが、ログによって初期化の流れに注意点があることに気づきました。

まず`super.init`で親クラスのプロパティが初期化されますよね。
コードの方で`3. backgroundColor did set`の位置が違うってのは、`init`で初期化された後にコードで`backgroundColor`をセットしているためです。`init(frame: frame)`で初期化した場合は`frame`だけこのタイミングで初期化されるということです。

問題はここではなく、サブクラスで新しく設けたプロパティ（`title`）を`@IBInspectable`でセットしたときにどのタイミングで初期化されるかです。

筆者は`frame`や`backgroundColor`が初期化されるタイミングと同じだと勝手に思っていました。
実際には、`init`内の処理が終わった後になるようですね。コードで生成する場合は、`init(frame: frame)`→`backgroundColor`のセット→`title`のセットと書いたコードの順序に従っているので誤解は生じませんが、storyboardで生成した場合はコードを見てもこの流れが認識しづらいという印象を受けました。