web-dev-qa-db-ja.com

UIViewControllerは無効なフレームを返しますか?

ViewControllerをlandscapeモードで起動すると(viewDidLoadが呼び出された後)、代わりにポートレートモードのフレームサイズが得られるフレームを印刷します。

これはバグですか?

- (void)viewDidLoad
{
   [super viewDidLoad];

   NSLog(@"%@", NSStringFromCGRect(self.view.frame));
   // Result is (0, 0 , 768, 1024)
}
38
aryaxt

わからないことがいくつかあります。

まず、システムは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:およびbeforeviewWillLayoutSubviewsを受け取ります。

viewWillLayoutSubviewsおよびviewDidLayoutSubviewsメソッドは、iOS 5.0の新機能です。

layoutSubviewsメッセージはビューコントローラーではなくビューに送信されるため、使用する場合はカスタムUIViewサブクラスを作成する必要があります。

158
rob mayoff

これがクリーンなソリューションです。

同じ問題が発生しており、アプリ全体で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;
}
3
Timo

これが代替ソリューションです(Swift 4):

override func viewDidLoad() {
    super.viewDidLoad()
    self.view.setNeedsLayout()
    self.view.layoutIfNeeded()
}

基本的に、これにより、ビューコントローラーはビューを正しくレイアウトし、そこからすべてのサブビューの正しいフレームを取得できます。これは特に、トランジションアニメーションを実行し、ビューコントローラーがautolayoutとインターフェイスビルダーを使用している場合に役立ちます。

私が気付いたことから、初期フレームは、インターフェイスビルダーのデフォルトサイズクラスに設定されているものに設定されているようです。私は通常iPhone XSサイズクラスを使用して編集するため、viewDidLoadでは、iPhone XRを使用しているかどうかに関係なく、ビューの幅は常に375であるようです。これはviewWillAppearの前に自動的に修正されます。

上記のコードはこの問題を修正し、ビューコントローラーが画面にレンダリングされる前にビュー/サブビューの正しいフレームを取得できるようにします。

0
Nick Kirsten