はじめに
iOSアプリ開発において、UIを部品化して使い回したい場合、XIBファイルを使って実現することが多いと思います。
実装方法を忘れることが多いので、本記事でまとめることにします。
環境
本記事ではXIBファイルの読み込みにR.swiftを使っています。
R.
から始まる処理はR.swiftの記述なので、適宜読み替えてください。
- OS:macOS Mojave 10.14.6
- Xcode:11.3.1 (11C504)
- Swift:5.1.3
- R.swift:5.2.2
カスタムビューの実装
カスタムビューの実装方法を紹介します。
カスタムビュークラスの作成
まず、 UIView
を継承したクラスを作成します。
私は↓をテンプレートとしてコピペしています。
import UIKit
@IBDesignable
final class FooView: UIView {
// MARK: Stored Instance Properties
// MARK: Computed Instance Properties
// MARK: IBOutlets
// MARK: Initializers
override init(frame: CGRect) {
super.init(frame: frame)
configureView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configureView()
}
// MARK: IBActions
// MARK: Other Internal Methods
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
configureView()
}
// MARK: Other Private Methods
private func configureView() {
loadNib()
// TODO: 他にUIの装飾処理があれば実行する
}
private func loadNib() {
guard let fooView = R.nib.fooView(owner: self) else {
fatalError("Fail to load FooView from Nib.")
}
fooView.frame = self.bounds
addSubview(fooView)
}
}
XIBファイルを編集してビルドしても画面が真っ白になることがありますが、大体はXIBファイルを読み込んで addSubview(_:)
していないのが原因です。
忘れやすいので、↑のように自分の中でテンプレート化しておくと便利です。
XIBファイルの作成
このままでは R.nib.fooView(owner: self)
でビルドエラーになるので、XIBファイルを作成します。
プロジェクトツリーで右クリック > New File... > iOS - User Interface - View > [Next]ボタンをクリック
XIBファイル名を入力 > [Create]ボタンをクリック
XIBファイル名は任意ですが、先ほど作成したカスタムビュークラスと同じにするとわかりやすいです。
XIBファイルのFile's Ownerを設定
XIBファイルのFile's Ownerに、先ほど作成したカスタムビュークラスを指定します。
忘れやすいので注意です。
XIBファイルから不要なものを非表示
画面全体でなく一部分のみのカスタムビューを作る場合、ViewのSizeを Freeform
、Top BarとBottom Barを None
にすると、不要なものが表示されなくなって実装しやすくなります。
こちらの設定はあくまでIB上でのみ反映され、実際のアプリには無関係という認識です。
そのため、作りたいカスタムビューに応じて設定を変更するのが望ましいです。
XIBファイルの編集
ここまで来たら好きなように UILabel
や UIButton
などのUIを配置し、カスタムビューを作成します。
必要に応じて IBOutlet
や IBAction
でカスタムビュークラスと接続してください。
カスタムビューの使い方
カスタムビューの使い方は、通常のUIと同様です。
StoryboardやXIBファイルに UIView
として配置し、Custom Classに指定するのみです。
カスタムビュークラスに @IBDesignable
を付けると、IB上で描画されてデザインが作りやすくなります。
注意
XIBファイルを使う際の注意点を紹介します。
XIBファイルから読み込んだビューはカスタムビューに載せているだけ
例えば、カスタムビュークラスで背景色を以下のように変更しようとします。
private func configureView() {
loadNib()
backgroundColor = .blue
}
これだけだと背景色は変わったように見えません。
理由は単純で、 loadNib()
内の処理を見ればわかります。
XIBファイルから読み込んだビューは、カスタムビュークラスのビューの上に追加しているので、いくら背景色を変えてもXIBのビューに隠されて見えないです。
private func loadNib() {
// XIBファイルからビューを読み込む
guard let fooView = R.nib.fooView(owner: self) else {
fatalError("Fail to load FooView from Nib.")
}
// XIBから読み込んだビューのサイズを、カスタムビュークラスのビューに合わせる
fooView.frame = self.bounds
// XIBから読み込んだビューを、カスタムビュークラスのサブビューに追加する
addSubview(fooView)
}
図で見るともっとわかりやすいと思います。
先ほどのコードで変えた背景色は、図の斜線部分なので、XIBのビューに隠されていることがわかります。
解決法は大きく2つあります。
- XIBファイル上でビューの背景色を透過色にする
最もかんたんな方法だが、透過色は計算負荷が高いのでパフォーマンスが落ちる可能性がある - XIBファイルから読み込んだビューを
contentView
のような名前でプロパティとして保持し、こちらの背景色を変える
プロパティとして持たせると、何でも操作できて無駄にスコープが広がる
パフォーマンスが落ちることはめったにないので、私は前者を選ぶことが多いです。
おわりに
これでXIBファイルを使ってカスタムビューを作れるようになりました!
UIを適切な粒度で部品化し、効率よく開発しましょう
もし本記事の内容に誤りや過不足があれば、ご指摘いただけると嬉しいです。
例えばよく見る awakeFromNib()
というメソッドを使わずに実現しているので、問題ないのか気になります><