引き継いだコードをリファクタリングしていたのですが、気づいたらこんなことになってました。
やってみたはいいんですが、これが良いのか悪いのかイマイチよくわからないので、ぜひ意見ください!
サンプル: Yaruki00/TransitionUtil
やったこと
ベース
以下のような画面遷移のコードを改善したい、とします。
// example1
let vc = UIStoryboard(name: "Board1", bundle: nil).instantiateViewController(withIdentifier: "Board1ViewController") as! Board1ViewController
vc.param1 = "Hello!"
self.present(vc, animated: true, completion: nil)
// example2
let vc = UIStoryboard(name: "Board2", bundle: nil).instantiateViewController(withIdentifier: "Board1ViewController") as! Board2ViewController
vc.param2 = 12345
vc.param3 = nil
self.navigationController?.pushViewController(vc, animated: true)
}
Storyboardを意識しなくていいようにする
どのStoryboardに所属しているかをいちいち確認したり考えたりするのは嫌なので、異なるStoryboardに所属するViewControllerでも同じようにインスタンス化できるようにします。
以下のような拡張を作ってみました。
extension UIStoryboard {
enum ViewController: String {
case Board1ViewController
case Board2ViewController
}
static func instantiateViewController(target: ViewController) -> UIViewController {
case .Board1ViewController:
return UIStoryboard(name: "Board1", bundle: nil).instantiateViewController(withIdentifier: target.rawValue)
case .Board2ViewController:
return UIStoryboard(name: "Board2", bundle: nil).instantiateViewController(withIdentifier: target.rawValue)
}
}
}
呼び出し側は以下のように変わります。
// before
let vc = UIStoryboard(name: "Board1", bundle: nil).instantiateViewController(withIdentifier: "Board1ViewController") as! Board1ViewController
let vc = UIStoryboard(name: "Board2", bundle: nil).instantiateViewController(withIdentifier: "Board1ViewController") as! Board2ViewController
// after
let vc = UIStoryboard.instantiateViewController(target: .Board1ViewController) as! Board1ViewController
let vc = UIStoryboard.instantiateViewController(target: .Board2ViewController) as! Board2ViewController
少し短くなったし、Storyboardを意識しないで良くなりました。
プロパティのセットと遷移もまとめる
遷移先がどんな画面でもプロパティのセットが同様にできるようKVCを使います。
以下のような拡張を作ってみました。
extension UIViewController {
func presentWithParams(target: UIStoryboard.ViewController, params: [String: Any?], animated: Bool, completion: (() -> Void)?) {
let vc = self.instantiateAndSetParams(target: target, params: params)
self.present(vc, animated: animated, completion: completion)
}
func navigationPushWithParams(target: UIStoryboard.ViewController, params: [String: Any?], animated: Bool) {
let vc = self.instantiateAndSetParams(target: target, params: params)
self.navigationController?.pushViewController(vc, animated: animated)
}
func instantiateAndSetParams(target: UIStoryboard.ViewController, params: [String: Any?]) -> UIViewController {
let vc = UIStoryboard.instantiateViewController(target: target)
for (key, value) in params {
vc.setValue(value, forKey: key)
}
return vc
}
}
上で作ったUIStoryboardの拡張で画面を生成し、KVCでプロパティをセットした後、画面遷移します。
呼び出し側は以下のように変わります。
// before
let vc = UIStoryboard.instantiateViewController(target: .Board1ViewController) as! Board1ViewController
vc.param1 = "Hello!"
self.present(vc, animated: true, completion: nil)
let vc = UIStoryboard.instantiateViewController(target: .Board2ViewController) as! Board2ViewController
vc.param2 = 12345
vc.param3 = nil
self.navigationController?.pushViewController(vc, animated: true)
// after
let params: [String: Any?] = [
"param1" : "Hello!",
]
self.presentWithParams(target: .Board1ViewController, params: params, animated: true, completion: nil)
let params: [String: Any?] = [
"param2" : 12345,
"param3" : nil
]
self.navigationPushWithParams(target: .Board2ViewController, params: params, animated: true)
スッキリしたような気がしなくもないです。
で、これってどうなの?
Storyboardの拡張について
良い
- どのStoryboardに所属するか呼び出し側では気にしなくてよい
悪い
- 画面を追加するたび列挙体とcaseのどっかに追記するのが若干面倒
悪い点はそれほどマイナスでもないので、やっていっていいかなーと思ってます。
ViewControllerの拡張(KVC)について
良い
- 遷移先がどんなプロパティを持っていても同じ関数が使える
- 次の画面にセットする値をまとめて書くので、代入文をつらつら書かれるよりかはわかりやすいかも
悪い
- プロパティ名や型を間違えていてもエラーにならない(サンプルコードでは実行時に落ちます)
静的解析の恩恵を受けられないと言うのは大きなマイナスかなと思います。
処理が共通化できるので、画面遷移のときに必ず何かする、みたいなときは有効かもしれませんが、別の方法でいい気もします。
おわりに
Storyboardを分割しようという記事は良く見ますが、インスタンス化を共通化するみたいな記事は見たことない気がします。皆さんどうしているんでしょうか。
僕の調べが足りないだけかもしれないので、そういうの扱っている記事があったらぜひ教えてください。
KVCに関しては、なぜかもとのコードで使っていたので、それを引き継ぐ形でリファクタリングしました。
面白い機能だとは思うのですが、今回の使い方はなんか違う気がします。どちらかと言うと代入よりも、代入を監視するための機能なんでしょうか。
ここらへんも全然調べられてないので、調査していきたいと思います。