前書き
本命/義理チョコを今か今かと待ちわびています、ナガクラ(@nagakuta)です!一ヶ月ぶりの投稿になります!
今回は上級者が開発時に使うといわれる、ちょこっと役立つTipsを紹介します!(バレンタインだけに)
TL;DR
viewDidLoad
をスリムにするには
- XIBでViewを実装する場合は
didSet
を使う - コードでViewを実装する場合は
Closure
を使う
太り過ぎなviewDidLoad
皆さんは、Viewを実装する際にどの方法でUIパーツの設定を行いますか?(´ε`;)ウーン…
自分は、今回の方法を知るまでは以下の方法を採っていました。
- XIBでCustom Viewのレイアウトを作成する場合
Interface Builder
にて設定 orviewDidLoad
内でコードによる設定
- コードでCustom Viewを実装する場合
loadView
でCustom Viewを実装、viewDidLoad
で細かい設定を実装
しかし、上記の方法では、UIパーツの数が多ければ多いほどviewDidLoad
が肥大化してしまい、最悪の場合は何十行もあるviewDidLoad
になってしまうこともありましたorz
final class CustomViewController: UIViewController { @IBOutlet private weak var firstLabel: UILabel! @IBOutlet private weak var secondLabel: UILabel! @IBOutlet private weak var thirdLabel: UILabel! @IBOutlet private weak var fourthLabel: UILabel! @IBOutlet private weak var firstButton: UIButton! @IBOutlet private weak var secondButton: UIButton! @IBOutlet private weak var thirdButton: UIButton! // MARK: - Life Cycle Methods override func viewDidLoad() { super.viewDidLoad() // FirstLabelの設定 self.firstLabel.text = /* 省略 */ self.firstLabel.font = /* 省略 */ self.firstLabel.textColor = /* 省略 */ self.firstLabel.textAlignment = /* 省略 */ self.firstLabel.lineBreakMode = /* 省略 */ // SecondLabelの設定 self.secondLabel.text = /* 省略 */ self.secondLabel.font = /* 省略 */ self.secondLabel.textColor = /* 省略 */ self.secondLabel.textAlignment = /* 省略 */ self.secondLabel.lineBreakMode = /* 省略 */ (中略) // FirstButtonの設定 self.firstButton.setTitle("ボタン1", for: .normal) self.firstButton.setTitleColor(UIColor.red, for: .normal) self.firstButton.tintColor = /* 省略 */ // SecondButtonの設定 self.secondButton.setTitle("ボタン2", for: .normal) self.secondButton.setTitleColor(UIColor.blue, for: .normal) self.secondButton.tintColor = /* 省略 */ (省略) } }
(可読性が低い…低すぎるぞ…!😨😨😨)
それでは可読性が低いので、各UIパーツごとに設定用のメソッド(e.g. setupLabel
)を実装し、それをviewDidLoad
で呼び出す、という方法で回避を試みましたが…
final class CustomViewController: UIViewController { (省略) // MARK: - Life Cycle Methods override func viewDidLoad() { super.viewDidLoad() self.setupFirstLabel() self.setupSecondLabel() (中略) self.setupFirstButton() self.setupSecondButton() (省略) } } // MARK: - Private Methods extension CustomViewController { /// FirstLabelの設定 private func setupFirstLabel() { self.firstLabel.text = /* 省略 */ self.firstLabel.font = /* 省略 */ self.firstLabel.textColor = /* 省略 */ self.firstLabel.textAlignment = /* 省略 */ self.firstLabel.lineBreakMode = /* 省略 */ } /// SecondLabelの設定 private func setupSecondLabel() { self.secondLabel.text = /* 省略 */ self.secondLabel.font = /* 省略 */ self.secondLabel.textColor = /* 省略 */ self.secondLabel.textAlignment = /* 省略 */ self.secondLabel.lineBreakMode = /* 省略 */ } (中略) /// FirstButtonの設定 private func setupFirstButton() { self.firstButton.setTitle("ボタン1", for: .normal) self.firstButton.setTitleColor(UIColor.red, for: .normal) self.firstButton.tintColor = /* 省略 */ } /// SecondButtonの設定 private func setupSecondButton() { self.secondButton.setTitle("ボタン2", for: .normal) self.secondButton.setTitleColor(UIColor.blue, for: .normal) self.secondButton.tintColor = /* 省略 */ } (省略) }
(何だこれは…やたらメソッドが増えるじゃないか…😥😥😥)
結局のところ、この方法もベストとは言えない方法だと思っていました。
viewDidLoad
をシェイプアップする冴えた方法
なんとかviewDidLoad
を膨らませず、かつ設定用メソッドの実装以外の方法でUIパーツの設定を行えないか…
その方法を探るべく、インターネットを彷徨っていたところ、一件のページを見つけました。
その記事中には、
viewDidLoad
でUIパーツの設定やプロパティの初期化をするのは古臭いし、いい方法じゃないよね- Swiftでは、
didSet
とClosure
でプロパティの初期化をする方法があるって知ってた? - じゃあ、これらを使ってUIパーツの設定を行えばいいんじゃないかな
といったことが書かれていました。
「…え、Closure
でプロパティの初期化ができるの!?」と驚いた私がApple公式のSwiftガイドを見ると、たしかにClosure
でのプロパティ初期化の方法が載っているではありませんか。
https://docs.swift.org/swift-book/LanguageGuide/Initialization.html
なるほど、確かにこの方法をUIパーツに適用すれば、わざわざviewDidLoad
でゴリゴリと設定を実装しなくても良くなりそうです。
実際にやってみた
XIBでCustom Viewのレイアウトを実装している場合
XIBでCustom Viewのレイアウトを実装する場合、IBOutlet
でコードと結びつけてから設定を行います。
その場合はClosure
による初期化ができないので、didSet
を利用して設定します。
final class CustomViewController: UIViewController { @IBOutlet private weak var firstLabel: UILabel! { didSet { self.firstLabel.text = /* 省略 */ self.firstLabel.font = /* 省略 */ self.firstLabel.textColor = /* 省略 */ self.firstLabel.textAlignment = /* 省略 */ self.firstLabel.lineBreakMode = /* 省略 */ } } @IBOutlet private weak var secondLabel: UILabel! { didSet { self.secondLabel.text = /* 省略 */ self.secondLabel.font = /* 省略 */ self.secondLabel.textColor = /* 省略 */ self.secondLabel.textAlignment = /* 省略 */ self.secondLabel.lineBreakMode = /* 省略 */ } } @IBOutlet private weak var thirdLabel: UILabel! { didSet { (省略) } } @IBOutlet private weak var fourthLabel: UILabel! { didSet { (省略) } } @IBOutlet private weak var firstButton: UIButton! { didSet { self.firstButton.setTitle("ボタン1", for: .normal) self.firstButton.setTitleColor(UIColor.red, for: .normal) self.firstButton.tintColor = /* 省略 */ } } @IBOutlet private weak var secondButton: UIButton! { didSet { self.secondButton.setTitle("ボタン2", for: .normal) self.secondButton.setTitleColor(UIColor.blue, for: .normal) self.secondButton.tintColor = /* 省略 */ } } @IBOutlet private weak var thirdButton: UIButton! { didSet { (省略) } } // MARK: - Life Cycle Methods override func viewDidLoad() { super.viewDidLoad() (ここで何もしなくていい😄) } }
おお…!これならviewDidLoad
が初期化で膨れ上がることもないし、設定用のメソッドを書かなくてもいいし、どのUIパーツにどの設定がされているかが一目瞭然ですね😊😊😊
ちなみに、このdidSet
が呼ばれるタイミングはviewDidLoad
の前になります。IBOutlet
プロパティに値がセットされるタイミング(loadView
と同じタイミング…?勉強不足ですみません🙇)で各々のUIパーツの設定がなされます。
コードでCustom Viewを実装する場合
そして、Custom Viewをコードで実装する場合はClosure
で初期化を行います。
final class CustomViewController: UIViewController { private let firstLabel: UILabel! = { let label: UILabel = UILabel() label.text = /* 省略 */ label.font = /* 省略 */ label.textColor = /* 省略 */ label.textAlignment = /* 省略 */ label.lineBreakMode = /* 省略 */ return label }() private let secondLabel: UILabel! = { let label: UILabel = UILabel() label.text = /* 省略 */ label.font = /* 省略 */ label.textColor = /* 省略 */ label.textAlignment = /* 省略 */ label.lineBreakMode = /* 省略 */ return label }() private let thirdLabel: UILabel! = { let label: UILabel = UILabel() (中略) return label }() private let fourthLabel: UILabel! = { let label: UILabel = UILabel() (中略) return label }() private let firstButton: UIButton! = { let button: UIButton = UIButton() button.setTitle("ボタン1", for: .normal) button.setTitleColor(UIColor.red, for: .normal) button.tintColor = /* 省略 */ return button }() private let secondButton: UIButton! = { let button: UIButton = UIButton() button.setTitle("ボタン2", for: .normal) button.setTitleColor(UIColor.blue, for: .normal) button.tintColor = /* 省略 */ return button }() private let thirdButton: UIButton! = { let button: UIButton = UIButton() (中略) return button }() private lazy var customView: UIView = { [weak self] in let view: UIView = UIView(frame: UIScreen.main.bounds) // ここでViewを追加する view.addSubview(self?.firstLabel) view.addSubview(self?.secondLabel) (中略) view.addSubview(self?.firstButton) view.addSubview(self?.secondButton) (中略) return view }() // MARK: - Life Cycle Methods override func loadView() { self.view = self.customView } override func viewDidLoad() { super.viewDidLoad() (ここで何もしなくていい😄) } }
こちらもdidSet
の場合と同じく、どのUIパーツでどの設定がされているかが分かりやすいと思います👍
let
とlazy var
で宣言しているプロパティがありますが、その違いは「初期化時にプロパティの値を利用したいか」です。
let
の場合はプロパティを利用しなくてもいい場合です。lazy var
の場合は、weak self
でプロパティを弱参照にて利用することができます👍
あとがき
いかがでしょうか。はじめに載せたソースと最後に載せたソース、どちらが可読性の高いものだったでしょうか。
自分は、この方法をデファクトスタンダードにしたいと思っています(๑•̀ㅂ•́)و✧
なので、この記事を見て「良さそう!」と思った方、今すぐリファクタリングだ!!
追記
Qiitaのこちらの記事でも今回の記事と同様の内容が紹介されていました!こちらもぜひ参考にしてみてください!