Xcode8 UIViewController.view.subviewsのframeについて(追記しました)
ビューのサイズが1000x1000になる
Xcode8からStoryboardやxibでオブジェクトがframe値を持たなくなった影響で、UIViewController.view.subviewsのビューが正常なサイズを取得できなくて困るという話をよく耳にしている。
こんな感じのやつ
<UIView: 0x7ff823d04e80; frame = (0 0; 1000 1000); autoresize = RM+BM; layer = <CALayer: 0x61000003dca0>>
自分もこれで困ることはあったけど、まぁ仕方ないからラクしないで修正しようか。くらいの気持ちでした。
検証した
まずは通常のライフサイクルを確認する。
UIViewController
とUIView
を拡張してログ出力するようにした。
// UIViewControllerのログ
print("\(Date().description) : \(type(of: self)).\(#function):\(#line)")
// UIViewのログ
print("\(Date().description) : \(type(of: self)).\(#function):\(#line) : size: \(self.bounds)")
構成
// ViewControllerの構成(以下のログ表示名)
+ UINavigationController (NC)
|
+ UIViewController (VC)
// Viewの構成(以下のログ表示名)
+ VC.view(V1)
|
+ view(V2)
|
+ view(V3)
何もリサイズとかしなかった場合
UIViewController.viewDidLayoutSubviews()
が呼ばれた後でビューのサイズが取得できるようになりました。
// アプリを起動したところからログとってます
VC.init(coder:):19
NC.init(coder:):87
NC.awakeFromNib():92
VC.awakeFromNib():24
NC.viewDidLoad():97
NC.viewWillAppear:102
NC.viewWillLayoutSubviews():118
V3.init(coder:):29 : size: (0.0, 0.0, 1000.0, 1000.0)
V2.init(coder:):29 : size: (0.0, 0.0, 1000.0, 1000.0)
V1.init(coder:):29 : size: (0.0, 0.0, 375.0, 667.0)
V1.awakeFromNib():36 : size: (0.0, 0.0, 375.0, 667.0)
V2.awakeFromNib():36 : size: (0.0, 0.0, 1000.0, 1000.0)
V3.awakeFromNib():36 : size: (0.0, 0.0, 1000.0, 1000.0)
VC.viewDidLoad():30
VC.viewDidLoad():32
V1.setNeedsLayout():58 : size: (0.0, 0.0, 375.0, 667.0)
VC.viewWillAppear:37
V3.needsUpdateConstraints():78 -> true : size: (0.0, 0.0, 1000.0, 1000.0)
V2.needsUpdateConstraints():78 -> true : size: (0.0, 0.0, 1000.0, 1000.0)
V1.needsUpdateConstraints():78 -> true : size: (0.0, 0.0, 375.0, 667.0)
V1.layoutMarginsDidChange():73 : size: (0.0, 0.0, 375.0, 667.0)
V3.updateConstraints():68 : size: (0.0, 0.0, 1000.0, 1000.0)
V2.updateConstraints():68 : size: (0.0, 0.0, 1000.0, 1000.0)
VC.updateViewConstraints():46
V1.updateConstraints():68 : size: (0.0, 0.0, 375.0, 667.0)
V1.setNeedsLayout():58 : size: (0.0, 0.0, 375.0, 667.0)
VC.viewWillLayoutSubviews():53
V1.layoutSubviews():48 : size: (0.0, 0.0, 375.0, 667.0)
VC.viewDidLayoutSubviews():62
V2.layoutSubviews():48 : size: (0.0, 0.0, 375.0, 603.0)
V3.layoutSubviews():48 : size: (0.0, 0.0, 187.5, 301.5)
V3.layoutSubviews():48 : size: (0.0, 0.0, 187.5, 301.5)
V1.layoutIfNeeded():53 : size: (0.0, 0.0, 375.0, 667.0)
NC.viewDidLayoutSubviews():127
NC.viewWillLayoutSubviews():118
V1.layoutIfNeeded():53 : size: (0.0, 0.0, 375.0, 667.0)
NC.viewDidLayoutSubviews():127
V1.draw:41 : size: (0.0, 0.0, 375.0, 667.0)
V2.draw:41 : size: (0.0, 0.0, 375.0, 603.0)
V3.draw:41 : size: (0.0, 0.0, 187.5, 301.5)
V1.layoutIfNeeded():53 : size: (0.0, 0.0, 375.0, 667.0)
V1.layoutIfNeeded():53 : size: (0.0, 0.0, 375.0, 667.0)
VC.viewDidAppear:42
NC.viewDidAppear:107
レイアウトの再計算を仕込んだ場合
次にUIViewController.viewDidLoad()
でUIView.layoutIfNeeded()
を読んだ場合、UIViewController.viewDidLoad()
でUIView.layoutIfNeeded()
が呼ばれた後からビューのサイズが取得できるようになりました。
UIViewController.awakeFromNib()
でもだいたい同じでした。
VC.init(coder:):19
NC.init(coder:):87
NC.awakeFromNib():92
VC.awakeFromNib():24
NC.viewDidLoad():97
NC.viewWillAppear:102
NC.viewWillLayoutSubviews():118
V3.init(coder:):29 : size: (0.0, 0.0, 1000.0, 1000.0)
V2.init(coder:):29 : size: (0.0, 0.0, 1000.0, 1000.0)
V1.init(coder:):29 : size: (0.0, 0.0, 375.0, 667.0)
V1.awakeFromNib():36 : size: (0.0, 0.0, 375.0, 667.0)
V2.awakeFromNib():36 : size: (0.0, 0.0, 1000.0, 1000.0)
V3.awakeFromNib():36 : size: (0.0, 0.0, 1000.0, 1000.0)
VC.viewDidLoad():30
V3.updateConstraints():68 : size: (0.0, 0.0, 1000.0, 1000.0)
V2.updateConstraints():68 : size: (0.0, 0.0, 1000.0, 1000.0)
VC.updateViewConstraints():46
V3.needsUpdateConstraints():78 -> false : size: (0.0, 0.0, 1000.0, 1000.0)
V2.needsUpdateConstraints():78 -> false : size: (0.0, 0.0, 1000.0, 1000.0)
V1.needsUpdateConstraints():78 -> true : size: (0.0, 0.0, 375.0, 667.0)
V1.updateConstraints():68 : size: (0.0, 0.0, 375.0, 667.0)
V1.setNeedsLayout():58 : size: (0.0, 0.0, 375.0, 667.0)
V1.setNeedsLayout():58 : size: (0.0, 0.0, 375.0, 667.0)
VC.viewWillLayoutSubviews():53
V1.layoutSubviews():48 : size: (0.0, 0.0, 375.0, 667.0)
VC.viewDidLayoutSubviews():62
V2.layoutSubviews():48 : size: (0.0, 0.0, 375.0, 667.0)
V3.layoutSubviews():48 : size: (0.0, 0.0, 187.5, 333.5)
V3.layoutSubviews():48 : size: (0.0, 0.0, 187.5, 333.5)
V1.layoutIfNeeded():53 : size: (0.0, 0.0, 375.0, 667.0)
VC.viewDidLoad():32
VC.viewWillAppear:37
V3.needsUpdateConstraints():78 -> false : size: (0.0, 0.0, 187.5, 333.5)
V2.needsUpdateConstraints():78 -> false : size: (0.0, 0.0, 375.0, 667.0)
V1.needsUpdateConstraints():78 -> false : size: (0.0, 0.0, 375.0, 667.0)
V1.layoutMarginsDidChange():73 : size: (0.0, 0.0, 375.0, 667.0)
V2.setNeedsLayout():58 : size: (0.0, 0.0, 375.0, 667.0)
V1.setNeedsLayout():58 : size: (0.0, 0.0, 375.0, 667.0)
VC.viewWillLayoutSubviews():53
V1.layoutSubviews():48 : size: (0.0, 0.0, 375.0, 667.0)
VC.viewDidLayoutSubviews():62
V2.layoutSubviews():48 : size: (0.0, 0.0, 375.0, 603.0)
V3.layoutSubviews():48 : size: (0.0, 0.0, 187.5, 301.5)
V1.layoutIfNeeded():53 : size: (0.0, 0.0, 375.0, 667.0)
NC.viewDidLayoutSubviews():127
NC.viewWillLayoutSubviews():118
V1.layoutIfNeeded():53 : size: (0.0, 0.0, 375.0, 667.0)
NC.viewDidLayoutSubviews():127
V1.draw:41 : size: (0.0, 0.0, 375.0, 667.0)
V2.draw:41 : size: (0.0, 0.0, 375.0, 603.0)
V3.draw:41 : size: (0.0, 0.0, 187.5, 301.5)
V1.layoutIfNeeded():53 : size: (0.0, 0.0, 375.0, 667.0)
V1.layoutIfNeeded():53 : size: (0.0, 0.0, 375.0, 667.0)
VC.viewDidAppear:42
NC.viewDidAppear:107
その他
ビューのサイズがおかしくなるのといっても、UIView
に問題があるわけではなく、xibやstoryboardから生成されたインスタンスのsubviewsがおかしくなるというところだったので、なるべくUIView
を拡張するような対応はしたくないな。と個人的には考えています。
もちろんUIView
のインスタンスの場合はそうも言ってられないのかなとは思うのですが、UIViewController
の方が出番多いし、そっちの問題を片付けられたらそれでいいよね。と思っています。
stack overflowではUIView.layoutSubviews()
のoverrideが推奨されているような感じですが、あまり気がすすまないです。
あと、今回のソースコードをgithubにあげました。
https://github.com/ikenie3/xcode8-frame
以下追記@2016/10/25
UIViewController
とUINavigationController
のログでUIViewController.topLayoutGuide.length
も追加で出力するようにしました。
// Before
print("\(Date().description) : \(type(of: self)).\(#function):\(#line)")
// After
print("\(Date().description) : \(type(of: self)).\(#function):\(#line), topLayoutGuide: \(self.topLayoutGuide.length)")
結果ですがUIViewController.updateViewConstraints
の時点でステータスバー+ナビゲーションバーの高さ取得できていました。
レイアウトの再描画を行わなかったときのログだけ出しました。
2016-10-25 08:09:56 +0000 : VC.init(coder:):19, topLayoutGuide: 0.0
2016-10-25 08:09:56 +0000 : NC.init(coder:):86, topLayoutGuide: 0.0
2016-10-25 08:09:56 +0000 : NC.awakeFromNib():91, topLayoutGuide: 0.0
2016-10-25 08:09:56 +0000 : VC.awakeFromNib():24, topLayoutGuide: 0.0
2016-10-25 08:09:56 +0000 : NC.updateViewConstraints():110, topLayoutGuide: 0.0
2016-10-25 08:09:56 +0000 : NC.viewDidLoad():96, topLayoutGuide: 0.0
2016-10-25 08:09:56 +0000 : NC.viewWillAppear:101, topLayoutGuide: 0.0
2016-10-25 08:09:56 +0000 : NC.viewWillLayoutSubviews():117, topLayoutGuide: 20.0
2016-10-25 08:09:56 +0000 : V3.init(coder:):29 : size: (0.0, 0.0, 1000.0, 1000.0)
2016-10-25 08:09:56 +0000 : V2.init(coder:):29 : size: (0.0, 0.0, 1000.0, 1000.0)
2016-10-25 08:09:56 +0000 : V1.init(coder:):29 : size: (0.0, 0.0, 375.0, 667.0)
2016-10-25 08:09:56 +0000 : V1.awakeFromNib():36 : size: (0.0, 0.0, 375.0, 667.0)
2016-10-25 08:09:56 +0000 : V2.awakeFromNib():36 : size: (0.0, 0.0, 1000.0, 1000.0)
2016-10-25 08:09:56 +0000 : V3.awakeFromNib():36 : size: (0.0, 0.0, 1000.0, 1000.0)
2016-10-25 08:09:56 +0000 : VC.viewDidLoad():29, topLayoutGuide: 0.0
2016-10-25 08:09:56 +0000 : VC.viewDidLoad():31, topLayoutGuide: 0.0
2016-10-25 08:09:56 +0000 : V1.setNeedsLayout():58 : size: (0.0, 0.0, 375.0, 667.0)
2016-10-25 08:09:56 +0000 : VC.viewWillAppear:36, topLayoutGuide: 0.0
2016-10-25 08:09:56 +0000 : V3.needsUpdateConstraints():78 -> true : size: (0.0, 0.0, 1000.0, 1000.0)
2016-10-25 08:09:56 +0000 : V2.needsUpdateConstraints():78 -> true : size: (0.0, 0.0, 1000.0, 1000.0)
2016-10-25 08:09:56 +0000 : V1.needsUpdateConstraints():78 -> true : size: (0.0, 0.0, 375.0, 667.0)
2016-10-25 08:09:56 +0000 : V1.layoutMarginsDidChange():73 : size: (0.0, 0.0, 375.0, 667.0)
2016-10-25 08:09:56 +0000 : V3.updateConstraints():68 : size: (0.0, 0.0, 1000.0, 1000.0)
2016-10-25 08:09:56 +0000 : V2.updateConstraints():68 : size: (0.0, 0.0, 1000.0, 1000.0)
2016-10-25 08:09:56 +0000 : VC.updateViewConstraints():45, topLayoutGuide: 64.0
2016-10-25 08:09:56 +0000 : V1.updateConstraints():68 : size: (0.0, 0.0, 375.0, 667.0)
2016-10-25 08:09:56 +0000 : V1.setNeedsLayout():58 : size: (0.0, 0.0, 375.0, 667.0)
2016-10-25 08:09:56 +0000 : VC.viewWillLayoutSubviews():52, topLayoutGuide: 64.0
2016-10-25 08:09:56 +0000 : V1.layoutSubviews():48 : size: (0.0, 0.0, 375.0, 667.0)
2016-10-25 08:09:56 +0000 : VC.viewDidLayoutSubviews():61, topLayoutGuide: 64.0
2016-10-25 08:09:56 +0000 : V2.layoutSubviews():48 : size: (0.0, 0.0, 375.0, 603.0)
2016-10-25 08:09:56 +0000 : V3.layoutSubviews():48 : size: (0.0, 0.0, 187.5, 301.5)
2016-10-25 08:09:56 +0000 : V3.layoutSubviews():48 : size: (0.0, 0.0, 187.5, 301.5)
2016-10-25 08:09:56 +0000 : V1.layoutIfNeeded():53 : size: (0.0, 0.0, 375.0, 667.0)
2016-10-25 08:09:56 +0000 : NC.viewDidLayoutSubviews():126, topLayoutGuide: 20.0
2016-10-25 08:09:56 +0000 : NC.viewWillLayoutSubviews():117, topLayoutGuide: 20.0
2016-10-25 08:09:56 +0000 : V1.layoutIfNeeded():53 : size: (0.0, 0.0, 375.0, 667.0)
2016-10-25 08:09:56 +0000 : NC.viewDidLayoutSubviews():126, topLayoutGuide: 20.0
2016-10-25 08:09:56 +0000 : V1.draw:41 : size: (0.0, 0.0, 375.0, 667.0)
2016-10-25 08:09:56 +0000 : V2.draw:41 : size: (0.0, 0.0, 375.0, 603.0)
2016-10-25 08:09:56 +0000 : V3.draw:41 : size: (0.0, 0.0, 187.5, 301.5)
2016-10-25 08:09:56 +0000 : V1.layoutIfNeeded():53 : size: (0.0, 0.0, 375.0, 667.0)
2016-10-25 08:09:56 +0000 : V1.layoutIfNeeded():53 : size: (0.0, 0.0, 375.0, 667.0)
2016-10-25 08:09:56 +0000 : VC.viewDidAppear:41, topLayoutGuide: 64.0
2016-10-25 08:09:56 +0000 : NC.viewDidAppear:106, topLayoutGuide: 20.0