UIViewController返回无效的框架?

39

当我在landscape模式下启动我的ViewController时(在viewDidLoad被调用之后),它给我的frame大小是竖屏模式的大小。

这是一个bug吗?有什么建议吗?

- (void)viewDidLoad
{
   [super viewDidLoad];

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

1
那么viewDidAppear呢? - Luke
3个回答

158

有几件事情你没有理解。

首先,系统在加载你的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,而不是 1024 x 748。

如果这是您的根视图,则您可能想查看视图的边界(bounds):

(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:viewWillLayoutSubviews 方法之间,你的视图边界会发生变化。 viewWillLayoutSubviewsviewDidLayoutSubviews 方法是 iOS 5.0 新增的。 layoutSubviews 消息被发送给视图而不是视图控制器,所以如果你想使用它,你需要创建一个自定义的 UIView 子类。

1
我听过的最好的解释之一。谢谢。 - Jon
对我来说关键的是使用边界而不是框架。谢谢! - M.Y.
这是我在这个主题上见过的最好的。谢谢。 - Tien Do
嗨,罗布,希望你能看到这个消息——你是如何获得控制器加载视图时调用方法的顺序转储的? - csano
1
我在每个方法中都放置了一个 NSLog - rob mayoff
非常感谢您提供的内容。简单的时间顺序方法列表非常有用,应该在苹果的文档中突出显示。更好的做法是为各种转换制作状态图,然后在边缘上按方法调用顺序进行排序。 - Benjohn

3
这里有一个干净的解决方案。
我遇到了同样的问题,而且我坚持在整个应用程序中使用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;
}

0

这里是另一种解决方案(Swift 4):

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

基本上,这会强制视图控制器正确布局其视图,从而您可以获得所有子视图的正确框架。当执行转换动画并且您的视图控制器正在使用自动布局和界面构建器时,这特别有帮助。

从我注意到的情况来看,初始框架似乎设置为您的界面构建器的默认大小类别。我通常使用iPhone XS大小类别进行编辑,因此在viewDidLoad中,无论您是否使用iPhone XR,视图的宽度似乎始终为375。不过,在viewWillAppear之前,这个问题就会得到纠正。

上述代码将纠正此问题,并允许您在视图控制器呈现到屏幕之前获取视图/子视图的正确框架。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接