こんにちは。iOS担当の遠藤です。
最近、私達のチームではUI実装をカスタムコンポーネントを使用して行うようにしました。今回はそのメリットと実装方法について紹介したいと思います。
はじめに
今までのUI実装では、カスタムビューごとにInterface Builderでテキストの色や、サイズを設定していました。
しかしこのやり方には、以下の問題がありました。
- 間違った色やサイズを設定してしまう恐れがある
- xibを作成するたびに色やサイズなどを設定するのは面倒
- UIを変更する際に、変更範囲が広くなってしまう
UI実装をする度にInterface Builderでテキストやボタンの色や数値を設定すると、間違った設定をしてしまう恐れがあります。そして、毎回この設定をInterface Builderで設定するのは面倒です。
また、変更箇所が多くなってしまうことも問題でした。
アプリを開発、運用する中で一番変化が多いのはUIです。テキストやボタンの色、テキストサイズやフォントなどを変更したいという要望は少なからずあると思います。
Xcode 9からはColor Literalが使用できるようになり、色の変更はとても簡単になりましたが、テキストサイズやフォントの変更は一括ではできません。
テキストサイズやフォントを変えるのに多くのxibを変更する必要が出てきます。すべてのxibを変更し、漏れがないかを確認するのは非常に大変です。
これらの問題を解決するために、カスタムコンポーネントを使用して実装することにしました。UIを実装する際の参考になれば幸いです。
カスタムコンポーネントとは?
プロダクトで使用するテキストやボタンのデザインを定義したクラスをここではカスタムコンポーネントと呼んでいます。
UILabelやUIButtonを継承して実装し、xibでデザインを構成するのにこのコンポーネントを使用します。
カスタムコンポーネントを使用すると
どう変わるか?
カスタムコンポーネントにデザインの定義をしているので、xibにカスタムコンポーネントを配置するだけでUI実装が済みます。 このことではじめに挙げた問題は以下のように解決されました。
😣 間違った色やサイズを設定してしまう恐れがある
✅ 毎回Interface Builderでデザインの設定を行わないのでデザインの設定ミスが無くなります。
😣 xibを作成する度に色やサイズなどを設定するのは面倒
✅ xibではカスタムコンポーネントを配置するだけなので、デザインの設定を行うのはカスタムコンポーネントを実装する際の一度だけになります。
😣 デザインを変更する際に、変更範囲が広くなってしまう
✅ カスタムコンポーネントのみを変更するだけで済みます。
はじめに挙げた問題が全て解決されました。カスタムコンポーネントを使用することでUIの実装が楽になり、変更にも強くなります。
実装について
カスタムコンポーネントの実装について説明します。
カスタムコンポーネントを実装する上での制約
カスタムコンポーネントを実装する上で、2つの条件と1つの要望がありました。
条件
1. カスタムコンポーネントを使用する際に、Interface Builderでデザインが確認できること
カスタムコンポーネントを使用することで、Interface Builderでデザインの確認ができなくなってしまうとあまり使い勝手はよくありません。
デザインを実装する上でも、運用をする上でもInterface Builderでデザインを確認できたほうがやりやすいです。
👍 | 👎 |
---|---|
2. 型を保つこと
コンポーネントをカスタマイズしたことで、UILabel
やUIButton
の型ではなくUIView
など別の型になってしまうと扱いづらくなってしまいます。
特に困るのが、xibにカスタムコンポーネントを配置した際に型が保てないと、Attributes Inspector
で要素の設定ができなくなってしまうことです。
👍 | 👎 |
---|---|
要望
カスタムコンポーネント単体でのデザインを、Interface Builderで確認できること
Interface Builderでカスタムコンポーネント単体のデザインを確認できないことで大きな支障はありません。 しかし、Interface Builderでカスタムコンポーネントのデザインを確認できることは便利なので、できるようにしたいです。
カスタムコンポーネントの実装
上記で挙げた3つの課題を満たすようにカスタムコンポーネントを実装します。
Intarface Builderでデザインの表示
カスタムコンポーネントのデザインをInterface Builderで表示したいので、まず、カスタムコンポーネントのクラスに@IBDesignable
を設定します。
@IBDesignable
を書くことで、Interface Builderを表示するときにビルドがはしり、デザインが反映されるようになります。
@IBDesignable
を書いただけでは、Interface Builderにデザインの反映はされないので、prepareForInterfaceBuilder()
メソッドを書きます。
prepareForInterfaceBuilder()
はInterface Builderでビルドされるときのみ呼ばれ、そのメソッドで書かれた内容がIntarface Builderに反映されます。
Interface Builderを表示するときにビルドされるのなら、その時呼ばれる初期化メソッドのinit(frame:)
にデザインの設定処理を書けば済むように思われます。
しかし、init(frame:)
にデザインの設定を書いてもInterface Builderには表示されません。
なぜなら、初期化が終わった後に、Interface Builderで設定されている値で上書きしてしまうからです。
// CustomButton.swift @IBDesignable class CustomButton: UIButton { override func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() } }
デザインの設定
次に、デザインの設定です。
デザインの設定はInterface Builderを使用せずに、コードで設定をします。
Interface Builderでデザインを設定したカスタムコンポーネントを使用するには、xibを読み込む必要があります。
カスタムビューを使用するときのように、UINib
を使用してxibを読み込んだ実装をしてみました。
// CustomButton.swift @IBDesignable class CustomButton: UIButton { override init(frame: CGRect) { super.init(frame: frame) } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override func awakeFromNib() { super.awakeFromNib() loadNib() } override func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() loadNib() } func loadNib() { let bundle = Bundle(for: type(of: self)) let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle) let button = nib.instantiate(withOwner: self, options: nil).first as! UIButton button.frame = bounds addSubview(button) } }
しかし、この実装では意図したように表示されません。UIButtonの上にxibから生成したボタンを貼り付けているため、このように二重に表示されます。
これを避けるにはUIButtonを継承せずに、UIViewで実装をすると解決します。
しかし意図したように表示できたとしても、型がUIView
になってしまうためこのやり方はできません。
なのでxibではなく、デザインの設定をコードで行います。
// CustomButton.swift @IBDesignable class CustomButton: UIButton { override init(frame: CGRect) { super.init(frame: frame) setupAttributes() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupAttributes() } override func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() setupAttributes() } private func setupAttributes() { layer.cornerRadius = 4.0 backgroundColor = UIColor(red: 0x33 / 0xFF, green: 0x33 / 0xFF, blue: 0x33 / 0xFF, alpha: 1.0) setTitleColor(.white, for: .normal) titleLabel?.font = UIFont.boldSystemFont(ofSize: 14.0) } }
デザインを設定している実装がsetupAttributes()
です。
このメソッドを、init(coder:)
、init(frame:)
,prepareForInterfaceBuilder()
で呼び出します。
xibを読み込んで生成される場合はinit(coder:)
が呼ばれ、コードから生成する場合はinit(frame:)
が呼ばれるのでどちらにもsetupAttributes()
を書いています。
Interface Builderでも表示するために、prepareForInterfaceBuilder()
でも書いているのでInterface BuilderでビルドされるときはsetupAttributes()
が2回呼ばれてしまいます。
2回呼ばれてしまいますが、Interface Builderでのビルド時のみということと、書かれる処理が色やサイズの設定で複数回呼ばれても結果が変わらないため、私達は問題にはならないという判断をしました。
カスタムコンポーネントのデザインをIntarface Builderで表示する
カスタムコンポーネントのデザインをコードで実装すると、デザイン通りに実装できているのかぱっと見てわからないです。
そして、カスタムコンポーネント単体をxibで確認できるようにしたいという要望がまだ解決できていません。
ですが、@IBDesignable
とprepareForInterfaceBuilder()
を設定しているので、カスタムコンポーネント単体をInterface Builderで確認できるようにすることもできるはずです。
そこで私達は、CustomButton.xib
というプレビュー用のxibを用意することで解決しました。
xibをAssistant Editorで表示しながらコードを書くことでカスタムコンポーネントのデザインを確認しながら実装できます。
このプレビュー用のxibはアプリにバンドルする必要がないので、Target MemberShip
のチェックを外しています。
これで全ての条件、要望が解決されました。
あとはこのカスタムコンポーネントをxibに配置すると、Intarface Builderで色やフォントを設定しなくても実装できるようになります。
まとめ
今回はカスタムコンポーネントを使用したUIの実装について紹介しました。
アプリ開発において、UIを実装することは多く、実装の度に何回も同じ設定をするのは面倒です。また、単純な作業なため気をつけていたとしてもミスが起きてしまいます。カスタムコンポーネントを使用することで、毎回の煩わしい作業とミスをなくすことができました。デザインを変更するときも、カスタムコンポーネントのみの対応で済みます。変更範囲が狭いので対応漏れの不安がなくなります。
変更に強くミスのないUI実装になるので、カスタムコンポーネントを導入することをおすすめします。ぜひ、試してみてください。
弊社ではUI実装の仕組み化にも積極的に取り組んでいます。少しでも興味がある方は、ぜひ一度オフィスにお越しください。 下記からのエントリーもお待ちしています。