カスタムViewをNibから初期化し、IBDesignableとIBInspectableで便利に使う
iOS開発をしていると、独自のViewを作りたい場合があります。
実現する方法は色々あると思うのですが、最近自分がやっている方法をまとめます。
(以下で実装しているコードは GitHub にすべてあげています)
目指すゴール
- Storyboard / Interface Buidler から初期化できる
- コードからも初期化できる
- IBDesignable & IBInspectable に対応している
- 各画面から使いやすい(汎用性が高い)
上記を満たすカスタムViewを目指します。
こんな感じの、ユーザーにレビューをお願いするViewで、
↓みたいにStoryboard上から値をセットできるものを作ってみます。
実装の流れ
1. カスタムViewのxibとクラスを用意する
(1) xibを作成
MyCustomView.xib というファイルを作成します。
Size を "Freeform" にして適当にリサイズし、Status Bar を "None" にします。
(2) コンポーネントの配置とレイアウト
上から UILabel, UIImageView, UIButton を追加して AutoLayout をセットします。
(3) クラスの作成
MyCustomView クラスを UIView のサブクラスとして作成します。
class MyCustomView: UIView { ... }
2. xibとクラスを紐づける
xib を 開き、File's Owner にカスタムViewのクラス名を入力します。
View とではなく、File's Owner と紐づけます。
3. 初期化のコードを書く
以下のように初期化の実装を記述します。
class MyCustomView: UIView { // コードから初期化はここから override init(frame: CGRect) { super.init(frame: frame) comminInit() } // Storyboard/xib から初期化はここから required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) comminInit() } // xibからカスタムViewを読み込んで準備する private func comminInit() { // MyCustomView.xib からカスタムViewをロードする let bundle = NSBundle(forClass: self.dynamicType) let nib = UINib(nibName: "MyCustomView", bundle: bundle) let view = nib.instantiateWithOwner(self, options: nil).first as! UIView addSubview(view) // カスタムViewのサイズを自分自身と同じサイズにする view.translatesAutoresizingMaskIntoConstraints = false let bindings = ["view": view] addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[view]|", options:NSLayoutFormatOptions(rawValue: 0), metrics:nil, views: bindings)) addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options:NSLayoutFormatOptions(rawValue: 0), metrics:nil, views: bindings)) }
init(frame: CGRect)
はコードから初期化する場合に呼ばれ、
init(coder aDecoder: NSCoder)
は Storyboard/xib からの初期化で呼ばれます。
ここでは初期化の処理を commonInit()
メソッドに共通化してどちらからも呼んでいるので、コードからでも Storyboard/xib からでも初期化して使うことができます。
4. IBOutlet と IBAction を設定する
xibからCtrl+ドラッグで、IBOutlet や IBAction を設定します。
@IBOutlet weak private var titleLabel: UILabel! @IBOutlet weak private var iconImageView: UIImageView! @IBOutlet weak private var okButton: UIButton! // 「ALPACAを応援する」ボタンが押された際の挙動 @IBAction func okButtonTouched(sender: AnyObject) { let appStoreUrl = "https://itunes.apple.com/app/id934444072?mt=8" if let URL = NSURL(string: appStoreUrl) { if UIApplication.sharedApplication().canOpenURL(URL) { UIApplication.sharedApplication().openURL(URL) } } }
ここまでで動作としては完成です。
5. @IBDesignable を指定する
すでに動くものはできていますが、さらに使いやすく工夫していきます。
Xcode 6以降では、@IBDesignableを対象クラスに指定することで、Storyboard 上でカスタムViewの見た目を確認することができます。
@IBDesignable class MyCustomView: UIView { ...
上記のように記述すると、Storyboard上では以下の右のように表示されます。
便利ですね!
6. @IBInspectable を設定する
さらに便利に使うために、@IBInspectable を設定します。
これも @IBDesignable と同じく Xcode 6 から使えるようになったもので、設定するとカスタムクラスのプロパティの値をStoryboard上からセットできます。
上記のようにするためには、MyCustomView.swift に以下のように記述します。
@IBDesignable class MyCustomView: UIView { // viewの枠線の色 @IBInspectable var borderColor: UIColor = UIColor.clearColor() { didSet { self.layer.borderColor = borderColor.CGColor } } // viewの枠線の太さ @IBInspectable var borderWidth: CGFloat = 0 { didSet { self.layer.borderWidth = borderWidth } } // viewの角丸 @IBInspectable var cornerRadius: CGFloat = 0 { didSet { self.layer.cornerRadius = cornerRadius self.layer.masksToBounds = true } } // 「レビューのおねがい」部分のテキスト @IBInspectable var titleText: String = "" { didSet { titleLabel.text = titleText } } // アルパカが表示されている画像 @IBInspectable var iconImage: UIImage? { didSet { iconImageView.image = iconImage } } // 「ALPACAを応援する」ボタンのテキスト @IBInspectable var buttonTitle: String = "" { didSet { okButton.setTitle(buttonTitle, forState: .Normal) } }
カスタムクラスのプロパティは、実はこれまでも User Defined Runtime Attributes を使えば値のセットは可能でした。
User Defined Runtime Attributes
しかし、
- 不正な値をセットしてしまうとクラッシュしてしまう
- ここの存在を知らない開発者がいると動作を理解しづらい
など使いづらい部分があったため、今後は IBInspectable を使うのが良いでしょう。
使い方
以上でカスタムView側の実装は完成です。
最後にこのカスタムViewの使い方です。
Storyboard/xibから初期化して使う場合
UIView を Storyboard に貼り付け、クラス名に MyCustomView を指定します。
Storyboard上の表示を確認しながら値を変更します。
コードから初期化して使う場合
frame または AutoLayout の制約でサイズを指定し、addSubview して使います。
let customView = MyCustomView(frame: CGRectMake(50, 200, 280, 200)) view.addSubview(customView)
まとめ
カスタムViewをNibから初期化し、IBDesignable と IBInspectable を利用して便利に使えるようにする手順でした。
より良い方法、より簡単な方法があれば教えていただけますと幸いです。
サンプルコード
今回使ったコードはこちらに置いています。