iPhoneでテザリング中にレイアウトが崩れる問題の解決方法


TL;DR


  • iPhoneでテザリング中、UITabBarが画面からはみ出したりすることへの対応方法です。

  • RootViewControllerのviewにUITabBarControllerのviewをaddSubview:するときは、親viewの中に収まるようにAutoLayoutを設定します。


  • scrollView.contentInsetの調整にはtopLayoutGuide.topを使います。

  • サンプルアプリ


何が起きていたか


  • テザリング中や通話中などにレイアウトが崩れる


  • UITabBarが20pts下がって、画面からはみ出しまう

before.png


ViewController構成


UIViewController   // RootViewController

|- UITabBarController
|- UINavigationController
| |- UITableViewController
|- UINavigationController
|- UITableViewController


  • RootViewController内のviewDidLoadUITabBarControllerをコードで追加しています


  • UITabBarControllerをRootViewControllerとするXcode Projectでは、この問題は発生しません


Viewデバッガで見てみる

下記のような位置関係になっているため、 UITabBar が切れて見えます。

// UIWindowからの相対的なframe

UIWindow: (0, 0, 375, 667)
RootViewController: (0, 20, 375, 647)
UITabBarController: (0, 40, 375, 647)

before_view_debugger.png


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)
}
}


## 修正結果

after.png

after_view_debugger.png

UITabBarControllerのviewはRootViewControllerのviewと同じ位置、サイズになりました。


コンテンツ開始位置のズレ

このサンプルでは特に問題はありませんが、テザリング中にUITableViewのコンテンツ開始位置がズレる現象もよく見かけます。


ステータスバーのサイズ

UIApplicationstatusBarFrameが変わります。

テザリング中は見た目通り、高さが40で返ってきます。

// 通常時

statusBarFrame: (0, 0, 375, 20)

// テザリング中
statusBarFrame: (0, 0, 375, 40)

しかし、上記のViewデバッガで見てわかるように、RootViewControllerが20だけ下がります。

下記のようなコードを書くと、通常時と比べてコンテンツ開始位置が20だけ下がって見えてしまうでしょう。

// 通常時: 20 / テザリング時: 40

scrollView.contentInset.top = statusBarFrame.height


topLayoutGuide を使う

UIViewControllertopLayoutGuideはテザリング中でも値が変わりません。

topLayotGuide.topはステータスバーやナビゲーションバーの高さも考慮した値を返します。

ランドスケープ時にステータスバーが消えた場合は、ナビゲーションバーの高さだけ返してくれます。

// 通常時: 20 / テザリング時: 20

scrollView.contentInset.top = topLayoutGuide.top


所感

特に情報が見つからなかったので、独自の解決策です。

もっと良い方法や、Appleの公式なドキュメントがあれば教えて下さい。

UITabBarControllerをRootViewControllerとしてStoryboardで実装した場合は、今回の問題は発生しませんでした。

国内外・有名無名問わず、いろんなアプリで同じようなレイアウトの崩れがいくつか見られます。

開発者が意識することなく、うまいことiOS側で管理してほしいものです。