LoginSignup
4
2

More than 5 years have passed since last update.

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

Posted at

はじめに

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

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

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

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

初期化のタイミング

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

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).")
        }
    }

}

以下を実行します。backgroundColortitleをセットします。これをコードと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)

    }

screenshot.png

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


// 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のプロパティ

screenshot.png

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

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

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

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

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2