UIViewController的viewDidLoad方法中宽度/高度不正确

10

众所周知,在UIViewController的init/viewDidLoad方法中,不能信任frame size; 这样写:

- (void)viewDidLoad: {
     NSLog(@"%d", self.view.frame.size.width);
}

在许多情况下,它将打印错误的尺寸(特别是在横向模式下几乎无法使用)。

这实际上总是返回纠正后的结果,因此对于布局子视图非常有用:

- (void)viewWillAppear: {
     NSLog(@"%d", self.view.frame.size.width);
}

问题在于viewWillAppears每次视图出现时都会被调用,因此它不适合分配或添加子视图。 因此,您最终要在接口中声明每个单独的视图,并且由于大多数项目在初始设置后不需要进行任何操作,因此您最终会拥有我根本不喜欢的巨大标头文件。

因此,第一个问题是:是否有更好的处理子视图定位的方法?

第二个问题与此密切相关,假设我有一个包括各种其他子视图的UIView子类。 我在接口中声明它,并在我的init/viewDidLoad方法中分配/初始化它。

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    ...
    menu = [[SNKSlidingMenu alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
    ...
}

正如我们已经知道的那样,现在我们需要在viewWillAppear中重新定位它以获得更准确的读数。

- (void)viewWillAppear:(BOOL)animated{
    ....
    menu.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
    ....
 }

问题在于,所有的子视图都需要重新定位。这是通过自动调用的 layoutSubviews 函数完成的,但我们有同样的问题:所有子视图都需要在 SNKSlidingMenu 类的接口中声明。是否有绕过这个问题的方法?

谢谢。


1
使用自动布局还是设置自动调整掩码? - jrturton
4个回答

11

如果您的目标是 iOS 5.0 或更高版本,您可以使用 viewWillLayoutSubviewsviewDidLayoutSubviews 来进行更改。

至于您的第二个问题,如果您需要在 init 之外的其他方法中访问实例变量,则需要一直保留它,我认为这没有问题。

但是,您可以尝试使用自动布局并在子视图之间设置规则,以便自动进行布局而无需保留引用。


同意,但是每次初始化每个视图都必须使用initWithFrame似乎很愚蠢。对我来说,这似乎有很多重复的代码,并且似乎强制你使用很多实例变量。自动布局仅适用于较新的iOS。 - ksn
旧的 autoresizingMask 对你也许已经足够了。至于重复的代码:如果你正在复制大量的代码,可以创建自定义的 UIView 子类,这样你就不需要复制了,或者有帮助方法可以为您创建视图。如果您发布更多的代码,我们可能能够提供更多帮助。 - pgb

6

viewDidLoad 只有在视图被 创建 时才会被调用,但很多因素都会影响到 frame 的大小,并且当 frame 改变时不会再次被调用。

因此:

  • viewDidLoad 中创建子视图。
  • viewWillLayoutSubviews 中设置它们的大小。

关于处理旋转,请参见这里的额外讨论:https://stackoverflow.com/a/16421170/1445366


5

viewWillLayoutSubviewsviewDidLayoutSubviews可以解决这个问题。
但是这两个方法会被执行多次。 以下是我用来获取正确的self.view.frame的代码。

- (void)viewDidLoad {
  [super viewDidLoad];  
  ...  
  dispatch_async(dispatch_get_main_queue(), ^{  
   // init you view and set it`s frame. this can get correct frame.   
   ...  
  }  
  ...  
}

1
这是唯一对我有效的解决方案。尽管它有点丑陋,因为在dispatch_async启动之前会弹出一些奇怪的初始视图。对于Swift4:DispatchQueue.main.async { } - Adrian Bartholomew

2

这个东西在我多次使用中救了我不少的命(Swift 4):

最初的回答:

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

基本上,这会强制视图控制器正确布局其视图,从而可以获得所有子视图的正确框架。当执行过渡动画并且您的视图控制器正在使用自动布局和界面构建器时,这特别有帮助。
据我观察,初始框架似乎设置为您的界面构建器默认的大小类。我通常使用iPhone XS大小类进行编辑,因此在viewDidLoad中,无论您是否使用iPhone XR,视图的宽度似乎始终为375。不过,在viewWillAppear之前会自行纠正。
上述代码将纠正此问题,并允许您在呈现到屏幕之前获取视图/子视图的正确框架。

3
从xib文件创建vc,发现无法工作。 - daleijn
你救了我的命。我从今早开始就一直在做这个。 - Haroun Hajem

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