ViewControllerをlandscape
モードで起動すると(viewDidLoadが呼び出された後)、代わりにポートレートモードのフレームサイズが得られるフレームを印刷します。
これはバグですか?
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"%@", NSStringFromCGRect(self.view.frame));
// Result is (0, 0 , 768, 1024)
}
わからないことがいくつかあります。
まず、システムはnibをロードした直後にviewDidLoad
を送信します。ビューはまだビュー階層に追加されていません。したがって、デバイスの回転に基づいてビューのサイズを変更することもありません。
次に、ビューのフレームはスーパービューの座標空間にあります。これがルートビューの場合、スーパービューはUIWindow
になります(システムが実際にビューをビュー階層に追加すると)。 UIWindow
は、サブビューの変換を設定することで回転を処理します。これは、ビューのフレームが必ずしも期待どおりにならないことを意味します。
縦向きのビュー階層は次のとおりです。
(lldb) po [[UIApp keyWindow] recursiveDescription]
(id) $1 = 0x09532dc0 <UIWindow: 0x9632900; frame = (0 0; 768 1024); layer = <UIWindowLayer: 0x96329f0>>
| <UIView: 0x9634ee0; frame = (0 20; 768 1004); autoresize = W+H; layer = <CALayer: 0x9633b50>>
これが横向き左向きのビュー階層です。
(lldb) po [[UIApp keyWindow] recursiveDescription]
(id) $2 = 0x09635e70 <UIWindow: 0x9632900; frame = (0 0; 768 1024); layer = <UIWindowLayer: 0x96329f0>>
| <UIView: 0x9634ee0; frame = (20 0; 748 1024); transform = [0, -1, 1, 0, 0, 0]; autoresize = W+H; layer = <CALayer: 0x9633b50>>
横向きでは、フレームサイズは748 x 1024、not 1024 x 748です。
これがルートビューの場合、おそらく見たいのはビューの境界です。
(lldb) p (CGRect)[0x9634ee0 bounds]
(CGRect) $3 = {
(CGPoint) Origin = {
(CGFloat) x = 0
(CGFloat) y = 0
}
(CGSize) size = {
(CGFloat) width = 1024
(CGFloat) height = 748
}
}
おそらく、ビューの変換、フレーム、および境界がいつ更新されるかを知りたいでしょう。ビューコントローラーがビューを読み込むときにインターフェイスが横向きの場合、次の順序でメッセージを受け取ります。
{{0, 0}, {768, 1004}} viewDidLoad
{{0, 0}, {768, 1004}} shouldAutorotateToInterfaceOrientation:
{{0, 0}, {768, 1004}} shouldAutorotateToInterfaceOrientation:
{{0, 0}, {768, 1004}} viewWillAppear:
{{0, 0}, {768, 1004}} shouldAutorotateToInterfaceOrientation:
{{0, 0}, {768, 1004}} shouldAutorotateToInterfaceOrientation:
{{0, 0}, {768, 1004}} willRotateToInterfaceOrientation:duration:
{{0, 0}, {1024, 748}} viewWillLayoutSubviews
{{0, 0}, {1024, 748}} layoutSubviews
{{0, 0}, {1024, 748}} viewDidLayoutSubviews
{{0, 0}, {1024, 748}} willAnimateRotationToInterfaceOrientation:duration:
{{0, 0}, {1024, 748}} shouldAutorotateToInterfaceOrientation:
{{0, 0}, {1024, 748}} viewDidAppear:
ビューの境界が変化することがわかります後受け取るwillRotateToInterfaceOrientation:duration:
およびbeforeはviewWillLayoutSubviews
を受け取ります。
viewWillLayoutSubviews
およびviewDidLayoutSubviews
メソッドは、iOS 5.0の新機能です。
layoutSubviews
メッセージはビューコントローラーではなくビューに送信されるため、使用する場合はカスタムUIView
サブクラスを作成する必要があります。
これがクリーンなソリューションです。
同じ問題が発生しており、アプリ全体でframe
の使用を強く求めています。ルートビューコントローラーを例外にしたくありません。ルートビューコントローラーのframe
が縦方向の寸法を表示していることに気付きましたただし、すべてのサブビューは正しい横方向の寸法を持っています。
動作が異なるのはルートビューコントローラーだけなので、標準の空のビューコントローラーをルートビューコントローラーとして設定し、それにカスタムビューコントローラーを追加できます。 (以下のコード。)
その後、カスタムビューコントローラーで、意図したとおりにframe
を使用できます。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.rootViewController = [[UIViewController alloc] init];
[self.window makeKeyAndVisible];
self.viewController = [[ViewController alloc] initWithNibName:nil bundle:nil];
[self.window.rootViewController addChildViewController:self.viewController];
[self.window.rootViewController.view addSubview:self.viewController.view];
return YES;
}
これが代替ソリューションです(Swift 4):
override func viewDidLoad() {
super.viewDidLoad()
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
基本的に、これにより、ビューコントローラーはビューを正しくレイアウトし、そこからすべてのサブビューの正しいフレームを取得できます。これは特に、トランジションアニメーションを実行し、ビューコントローラーがautolayoutとインターフェイスビルダーを使用している場合に役立ちます。
私が気付いたことから、初期フレームは、インターフェイスビルダーのデフォルトサイズクラスに設定されているものに設定されているようです。私は通常iPhone XSサイズクラスを使用して編集するため、viewDidLoad
では、iPhone XRを使用しているかどうかに関係なく、ビューの幅は常に375であるようです。これはviewWillAppear
の前に自動的に修正されます。
上記のコードはこの問題を修正し、ビューコントローラーが画面にレンダリングされる前にビュー/サブビューの正しいフレームを取得できるようにします。