正确的 loadView 实现

Posted

技术标签:

【中文标题】正确的 loadView 实现【英文标题】:Correct loadView implementation 【发布时间】:2012-03-21 11:41:31 【问题描述】:

Apple 的文档没有说明 loadView 的正确实现是什么。

我发现如果你像这样实现 loadView:

- (void)loadView

    self.view = [[UIView alloc] init];

...那么你会得到与根本不实现它不同的行为。特别是,在一个 20 行的项目中,我发现 viewWillAppear 调用时 self.view 的大小为零 - 除非您使用 Apple 的默认版本的 loadView。

在 Google 上查看,有很多“教程”提供了明显错误的 loadView 实现 - 例如将大小强制设置为 (320,480),因为教程作者“发现如果我这样做就可以了”。

我想知道正确的实现应该是什么。

注意:在上面的示例中,我将其添加到 AppDelegate 内的视图层次结构中,如下所示:

[self.window addSubview:(UIViewController*).view];

我相信,在存在 UINavigationController 或 UITabBarController 的情况下,Apple 会做一些额外的魔法 - 作为副作用 - 导致单行 loadView 实现正常工作。但我想正确地编写它,以便它始终有效!

注意:我尝试在根视图上设置自动调整大小掩码,但它不会改变发生的情况:

- (void)loadView

    self.view = [[UIView alloc] init];
    self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

【问题讨论】:

【参考方案1】:

-loadView 的默认实现 创建视图或加载 NIB。据我所知,在-loadView在创建时无法知道视图的最终大小。所以默认视图大小设置为UIScreen.mainScreen.bounds。这是因为在-viewDidLoad 和其他方法中可能很难使用零帧视图。

您的单行实现可能如下所示:

- (void)loadView 
    self.view = [[UIView alloc] initWithFrame:UIScreen.mainScreen.bounds];

您不需要设置自动调整掩码,因为您不知道视图将在什么上下文中显示。调用者负责为您设置正确的框架、自动调整大小的掩码和类似的属性。

UINavigationController 方法中想象一下:

// we are pushing new VC, view is accessed for the first time
pushedVC.view.frame = CGRectMake(...);

它设置了正确的框架,但您的 -loadView 仅在 之前 被称为 -setFrame:。因此,在-viewDidLoad 期间,您有临时的非零帧,只是为了能够设置子视图和内部自动调整大小。在此之后,为您设置了正确的帧,并且在-viewWillAppear: 中您拥有最终帧。

【讨论】:

我怀疑这是正确的答案。我没有任何 ios 项目可以测试它——我所有的项目现在都大量使用 Storyboards 和 OpenGL 以及其他东西——所以我还没有标记为已接受。当我有时间制作一个虚拟项目时,我会检查一下。【参考方案2】:

首先,loadView 没有“默认”实现...该方法专门供您覆盖。我确实同意 Apple 的文档可能有点不清楚。但是loadView在导航控制器的视图被访问并且视图不存在时被默认调用(例如:UIView *view = viewController.view)。也可以手动调用。但在任何情况下loadView 都不会有正确的尺寸......事实上,这是不可能的。调用loadView 是为了让父视图控制器首先获取视图,以便它可以适当地调整它的大小。然后,一旦获得视图,它就会调用viewDidLoad。这是他们可以使用的唯一代码路径,因为视图可以从 loadView 方法或 nib 加载,并且当从 nib 加载视图时,它们必须提供额外设置的位置。最后,父控制器将调整视图大小并仅在视图实际出现时调用viewWillAppear。例如,如果您将控制器推送到屏幕外的 navController 上,它不会调用 viewWillAppear,直到 navController 本身被放置在屏幕上。这样做是因为在控制器真正可见之前运行该代码是没有意义的。这也是为什么您只能在 viewWillAppear 方法中获得正确尺寸的原因。

现在,您注意到,如果将控制器添加到标准控制器,则不会发生这些事情。这是因为视图控制器并不是真的打算包含其他视图控制器。现在在 iOS 5 中,它们明确支持使用 Container View Controllers……它本质上是一个视图控制器,旨在包含其他视图控制器。他们在 iOS 5 中添加了一些“方便”的方法来帮助解决这个问题,但这并不是绝对必要的。所有这一切的要点是:如果要将一个视图控制器添加到另一个视图控制器,则必须手动设置对子视图控制器的所有适当调用(所有加载方法、旋转事件、内存警告等)。换句话说,您必须制作自己的容器视图控制器。但是,当你这样做时,请记住我之前所说的关于代码路径的内容。请务必按照 Apple 的相同顺序调用子控制器方法,否则将无法正常工作。

以下是一些信息链接: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html -向下滚动到:实现容器视图控制器

这里还有视图控制器生命周期,这将帮助您确定需要按什么顺序进行哪些调用:http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html#//apple_ref/doc/uid/TP40007457-CH10-SW1

我确实建议阅读整个 View Controller Programming Guide....您可以从那里获得很多信息:http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007457-CH1-SW1

【讨论】:

感谢您的详细解答。但是,我仍然想知道为什么实现单行“loadView”从根本上改变了 Apple 的 UIKit 层次结构的行为 - 是否存在 不破坏标准代码的 loadView? loadView 的重点是创建和设置导航控制器的视图。现在,如果有与导航控制器关联的 nib 文件,导航控制器将改为加载 nib,您无需实现 loadView。但是如果没有 nib 则必须实现 loadView 来创建视图并将其分配给视图控制器的视图属性,否则将没有视图供父视图控制器调整大小。你得到 self.view.frame 的零帧,因为 self.view 等于 nil。你从来没有创造过它! 不,这是不正确的。请更仔细地阅读问题。如果我实现它,那么我会得到一个非零帧。如果我创建它,那么我会得到一个零帧。 你在用笔尖吗?这是我能想到的唯一会导致这种情况的事情。在我的 init 方法中,我将 self 初始化为 self = [super initWithNibName:nil bundle:nil];,这样就不会加载任何 nib 文件。然后容器视图控制器应该使用来自loadView 方法的视图来调整导航视图的大小。

以上是关于正确的 loadView 实现的主要内容,如果未能解决你的问题,请参考以下文章

iPhone - 让 loadView 条目填满父视图?

loadView

iOS:loadView 的推荐模式

如何在 loadView 中设置背景颜色?

loadView的使用总结

loadView与viewDidLoad