iOSアプリを開発しているエンジニアの庄司です。
今回は、iPhoneでのテザリング中や通話中に、ステータスバーの高さが変わることによる表示崩れの対応について紹介します。
TL;DR
- iPhoneでテザリング中、
UITabBarが画面からはみ出したりすることへの対応方法です。 - RootViewControllerのviewに
UITabBarControllerのviewをaddSubview:するときは、親viewの中に収まるようにAutoLayoutを設定します。 scrollView.contentInsetの調整にはtopLayoutGuide.topを使います。- サンプルアプリをGitHubにあげています。[GitHub]
何が起きていたか
- テザリング中や通話中などにレイアウトが崩れる
UITabBarが20pts下がって、画面からはみ出しまう
ViewController構成
UIViewController // RootViewController
|- UITabBarController
|- UINavigationController
| |- UITableViewController
|- UINavigationController
|- UITableViewController
- RootViewController内の
viewDidLoadでUITabBarControllerをコードで追加しています UITabBarControllerをRootViewControllerとするXcode Projectでは、この問題は発生しません
Viewデバッガで見てみる
下記のような位置関係になっているため、UITabBarがはみ出して見えます。
// UIWindowからの相対的なframe UIWindow: (0, 0, 375, 667) RootViewController: (0, 20, 375, 647) UITabBarController: (0, 40, 375, 647)
UITabBarがはみ出してしまう問題の対応
UITabBarControllerのviewをaddSubviewした後、AutoLayoutを設定してUITabBarControllerのviewがsuperviewの中に収まるようにします
Before
// RootViewController.swift override func viewDidLoad() { super.viewDidLoad() let tc: UITabBarController = createTabBarController() addChildViewController(tc) view.addSubview(tc.view) tc.didMoveToParentViewController(self) }
After
// RootViewController.swift override func viewDidLoad() { super.viewDidLoad() let tc: UITabBarController = createTabBarController() addChildViewController(tc) view.addSubview(tc.view) // view の中に収まるように、tabBarController.view に constraintを設定 view.addFittingConstraintsFor(tc.view) tabBarController.didMoveToParentViewController(self) } extension UIView { /** childViewが同じサイズに収まるように、constraintsを設定する - parameter childView: 子View */ func addFittingConstraintsFor(childView: UIView) { let constraints = [.Top, .Leading, .Bottom, .Trailing].map { NSLayoutConstraint( item: childView, attribute: $0, relatedBy: .Equal, toItem: self, attribute: $0, multiplier: 1.0, constant: 0.0) } childView.translatesAutoresizingMaskIntoConstraints = false addConstraints(constraints) } }
修正結果
UITabBarControllerのviewはRootViewControllerのviewと同じ位置、サイズになりました。
コンテンツ開始位置のズレ
このサンプルでは特に問題はありませんが、テザリング中にUITableViewのコンテンツ開始位置がズレる現象もよく見かけます。
ステータスバーのサイズ
UIApplicationのstatusBarFrameが変わります。
テザリング中は見た目通り、高さが40で返ってきます。
// 通常時 statusBarFrame: (0, 0, 375, 20) // テザリング中 statusBarFrame: (0, 0, 375, 40)
しかし、上記のViewデバッガで見てわかるように、RootViewControllerが20だけ下がります。
下記のようなコードを書くと、通常時と比べてコンテンツ開始位置が20だけ下がって見えてしまうでしょう。
// 通常時: 20 / テザリング時: 40 scrollView.contentInset.top = statusBarFrame.height
topLayoutGuide を使う
UIViewControllerのtopLayoutGuideはテザリング中でも値が変わりません。
topLayotGuide.topはステータスバーやナビゲーションバーの高さも考慮した値を返します。
ランドスケープ時にステータスバーが消えた場合は、ナビゲーションバーの高さだけ返してくれます。
// 通常時: 20 / テザリング時: 20 scrollView.contentInset.top = topLayoutGuide.top
所感
特に情報が見つからなかったので、独自の解決策です。 もっと良い方法や、Appleの公式なドキュメントがあれば教えて下さい。
UITabBarControllerをRootViewControllerとしてStoryboardで実装した場合は、今回の問題は発生しませんでした。
国内外・有名無名問わず、多くのアプリで同じようなレイアウトの崩れがいくつか見られます。 開発者が意識することなく、うまいことiOS側で管理してほしいものです。