この記事はニフティグループアドベントカレンダー22日目の記事です。
昨日は@jimmysharpさんで「監視システムPrometheusとTICK Stackを両方使ってみた」でした。
完成形
以下の動きを共通のコードで実現させることを目指します!
※設定アプリのような動きです
iPhone | iPad |
---|---|
背景
私が担当しているアプリでは↑に上げた完成形のような動きをします。
ですが、iPhoneとiPadで別々のコードで書かれており苦労したことが多々あったので、共通化のためのサンプルコードを作ろうと思いました。
どこに苦労したのか
- 画面の生成方法が違う
- iPhone :
UISplitViewController
使わない - iPad :
UISplitViewController
使う - 画面の初期化処理を2つ書かなければならず、変更時に片方書き忘れることがある
- iPhone :
- 詳細への画面遷移の方法が違う
- iPhone : 普通に
pushViewController(_:animated:)
- iPad : 詳細画面が生成されているかチェックし、あれば使う、なければ生成して使う
- iPadのときは常にinitを通るわけではないので、メソッドやプロパティへの直アクセスなどで依存するものを注入する必要があった
- nilかどうか気にしなきゃいけない
- いつ初期化されるかわからない
- iPhone : 普通に
- 上記の違いを吸収するコードを書かなければならない
- いたるところに
if iPhone {}
if iPad {}
... - iPhoneでは動くけどiPadのこと考慮から漏れてた!!というのが1度や2度じゃない…
- そして、吸収できないことが多々…🤮
- いたるところに
やってられん!!😡
ということで改善していきます!!💪
本題に入る前に…
私が担当するアプリでは
- iPhone : portrait(縦)固定
- iPad : landscape(横)固定
という仕様になっているため、サンプルコードも同様の設定にしてます。
画面回転を対応したい場合は、 UIViewController
の traitCollectionDidChange(_:)
でSizeClassの変更を検知して対応してください。
[iOS 8] マルチデバイス対応の新機能「Trait Collection」
共通化の手順
インプット
-
UISplitViewController
- 画面を分割して表示することができる
- SizeClassの横幅を元に分割表示できるかどうか判断する
- https://dev.classmethod.jp/references/ios8-trait-collection/
- 左側をMaster、右側をDetailと呼ぶ
- iOS8からはiPhoneでも使えるようになった!!👍
-
showDetailViewController(_:sender:)
: 今回の主役- SplitViewControllerでDetailを表示していたらDetailを差し替え
- 表示されていなかったら
navigationController.pushViewController(_:animated:)
する - 詳しい挙動はこちらの記事を参照
- 画面を分割して表示することができる
作ったもの
サンプルコード
GitHubAPIから取得したリポジトリ情報をTableViewに表示し、セルを押下すると該当のリポジトリのWebページを表示します。
肝は以下2つです。それぞれ説明します
SplitViewController
showDetailViewController(_:sender:)
SplitViewController
class SplitViewController: UISplitViewController, UISplitViewControllerDelegate {
// ...
override func viewDidLoad() {
super.viewDidLoad()
// 表示できるならMasterとDetail両方表示させる
preferredDisplayMode = .allVisible
delegate = self
viewControllers = [
UINavigationController(rootViewController: master),
detail
]
}
// 横幅が小さいときDetailをどう表示するかを決めるdelegate
// true : Detailを隠す
// false : Detailを表示させる
func splitViewController(_ splitViewController: UISplitViewController,
collapseSecondary secondaryViewController: UIViewController,
onto primaryViewController: UIViewController) -> Bool {
return splitViewController.isCollapseSecondary
}
}
extension UISplitViewController {
/// Detailを隠すかどうか
var isCollapseSecondary: Bool {
// 横幅が狭かったらDetailを隠す
return traitCollection.containsTraits(in: UITraitCollection(horizontalSizeClass: .compact))
}
}
showDetailViewController(_:sender:)
class SearchResultViewController: UITableViewDelegate, UITableViewDataSource {
// ...
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let url = repositories[indexPath.row].htmlUrl
let detailView = DetailViewController(url: url)
// Detailに表示されるかpushViewControllerされるかはおまかせ
showDetailViewController(detailView, sender: self)
}
}
まとめ
- iOS8からiPhoneでも使えるようになった
UISplitViewController
を活用して、iPhoneとiPadのUIを同一コードで実現できました。- Detailを一緒に表示させるかどうか
- 画面遷移のロジックを統一
- このサンプルを活用して、担当アプリも改善していきたいと思います!!💪💪
- UIの共通化つながりですが、SizeClassを用いてViewの並びや表示するコンポーネントを切り替えることもできますので、合わせてご参照ください。私もあとで試してみたいと思います。
- 最初は弊社で採用しているVIPERアーキテクチャで実装していましたが、サンプルにしてはあまりに壮大になったのでやめました。masterブランチはVIPERアーキテクチャでの実装になってますので気になる方は御覧ください。
最後に
明日は@taka_masaさんが「今年イチの脆弱性」についてお話いただけるそうです!お楽しみに!