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側で管理してほしいものです。