获取 UIViewController 视图的正确边界
Posted
技术标签:
【中文标题】获取 UIViewController 视图的正确边界【英文标题】:Getting the correct bounds of UIViewController's view 【发布时间】:2012-07-16 09:13:55 【问题描述】:我有一个 iPad 应用程序。在横向上,UIViewController
的视图实际宽度 = 1024px
和高度 = 768 - 20 (statusBar) - 44 (navigationBar) = 704px
。
所以我想得到这个[1024 x 704]
尺寸,我正在使用self.view.bounds
。它返回[748 x 1024]
,这是错误的!但是当我旋转屏幕两次(当前 -> 纵向 -> 当前)时,视图的边界是正确的 - [1024 x 704]
。
视图是这样初始化的:
- (void)loadView
self.view = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
self.view.backgroundColor = [UIColor lightGrayColor];
边界是这样的:
- (void)viewDidLoad
[super viewDidLoad];
NSLog(@"bounds = %@", NSStringFromCGRect(self.view.bounds));
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
NSLog(@"bounds = %@", NSStringFromCGRect(self.view.bounds));
所以问题是.. 我怎样才能在一开始就获得正确的视图绑定?
【问题讨论】:
在我的情况下,使用viewWillLayoutSubviews
工作但有一个我不喜欢的延迟(大约一秒)。我试过viewDidLayoutSubviews
,它工作正常。请记住,viewDidLayoutSubviews 可能会触发多次,具体取决于您对子视图的操作,因此您可能需要进行一些额外的检查。 developer.apple.com/documentation/uikit/uiviewcontroller/…
@MohsenHK 嗨——你描述的情况似乎没有意义。 viewDidLayoutSubviews
在viewWillLayoutSubviews
之后被调用,所以它的延迟不能少。那有意义吗?我认为您遇到的延迟一定是由于发生了其他事情——也许是与动画的交互?
【参考方案1】:
如何正确地做到这一点
您的UIViewController
子类应覆盖方法viewWillLayoutSubviews
、see also here。
调用此方法时,viewController 的view
具有正确的大小,您可以在布局通过子视图之前对子视图进行任何必要的调整。
斯威夫特
override func viewWillLayoutSubviews()
super.viewWillLayoutSubviews()
NSLog("bounds = \(self.view.bounds)")
Obj-C
- (void)viewWillLayoutSubviews
[super viewWillLayoutSubviews];
NSLog(@"bounds = %@", NSStringFromCGRect(self.view.bounds));
文档
当视图的边界发生变化时,视图会调整其子视图的位置。您的视图控制器可以覆盖此方法以在视图布置其子视图之前进行更改。这个方法的默认实现什么都不做。
正如您从强调部分看到的那样,每次视图控制器的视图更改大小以及视图首次出现时都会调用此方法。这使您可以正确响应旋转和其他边界更改事件
几种行不通的方法
其他答案中建议的一些方法效果不佳,或者存在严重缺陷。我敦促您避免使用这些方法,我将通过它们讨论您应该避免它们的原因。
viewDidLoad
和 viewWillAppear
- 在这些调用期间,view
还没有最终大小。 你得到的尺寸永远是正确的,纯属偶然,以免误导你。
viewDidAppear
– 为时已晚。 您的视图已显示在屏幕上并且对用户可见。在此处进行更改将导致可见的更改/突然的故障,并且看起来很业余。再一次,请——为了你,为了我,为了每个人:不要这样做!你比那更好,你的用户也是。
UIScreen.mainScreen.bounds.size
- 这是非常低级别。您正在实现UIViewController
,其视图的大小取决于它嵌套的控制器(导航、选项卡、分页、任何自定义控制器等)、设备的旋转方式以及屏幕的旋转方式分开进行多任务处理。因此,虽然您可能能够补偿所有这些并计算视图的最终大小,但如果 Apple 决定更改其中任何一个,您最终会得到复杂而脆弱的代码,这些代码很容易被破坏指标。如果您覆盖 viewWillLayoutSubviews
,UIViewController
将为您完成所有这些工作。
除了不提供正确的信息外,这些有问题的方法不会帮助您处理自动旋转或导致视图控制器的视图改变大小的其他事件,例如多任务手势。这是你真正想要顺利处理的事情。
所以请:成为冠军。以正确的方式去做。使用viewWillLayoutSubviews
。每次大小更改都会调用您的实施,您的用户、未来的自己和团队成员会为此庆祝。太棒了!
更多提示
当 viewWillLayoutSubviews
被调用时,层次结构中唯一会调整为最终大小的视图是viewController.view
。对此的赠品是以方法的名义进行的。它告诉你view…
(你的视图控制器的根view
)…WillLayout…
(现在很快,但它还没有发生还)…Subviews
(其他的一切都在它在根视图下的层次结构)。
所以子视图布局还没有发生。 根下的每个子还没有有一个有效的final大小。您从子视图中查询的任何尺寸信息充其量都是完全错误的。
更有可能,而且更糟,这将是误导性正确。
由于此大小和方向的默认设置,或者由于您的故事板设置,它恰好是您期望和需要的。但这只是偶然,对于不同的设备尺寸或方向,您不能依赖它。
如果您需要在特定子视图更改大小时被告知并知道其确切的最终大小,您通常应该覆盖该特定 UIView
子类的 layoutSubviews
方法。
【讨论】:
感谢您的回答!澄清一下,如果我们想知道UIView
类的子视图的大小,唯一正确的方法是重写其 layoutSubviews
方法(这需要创建自定义类)?
@Crashalot Overriding 对于布置子类的子类是有意义的。但是,如果您经常想要组合此行为而不是覆盖现有视图类,则可能会出现问题。我采取的一种方法是创建一个特定的UIView
子类BoundsSenderView
,它只有 具有将layoutSubviews
转发到某些注入的boundsListener
处理程序的行为。它不显示任何内容或响应触摸等。然后您可以使用自动布局将其固定到您想要大小的视图上,当它改变大小时您会得到回调。
可能还有其他方法可以在布局后获取子视图的大小,但我不知道它们。完成后,您需要连接到完整的布局通道。
感谢您的回复。令人沮丧的是,Apple 让这变得困难。【参考方案2】:
根据其他一些答案,您看到的问题是因为 viewDidLoad
在轮换发生之前被称为 。因为 iPad 总是以纵向模式初始化,所以如果您在 viewDidLoad
中获得尺寸值,它们将始终是纵向尺寸 - 这与您配置的任何方向无关。
要在方向/旋转发生后获取尺寸,请在viewDidAppear
中获取尺寸值。
我不太明白为什么 ios 不能更好地处理这个问题 - 特别是考虑到您在项目设置中以及在 Xcode Interface Builder 中定义了方向。但是,我确信这是有充分理由的 ;-)。
【讨论】:
如果您正在创建大量视图,则视图出现可能会出现延迟,您可以使用viewWillAppear
减少延迟
我试过 viewWillAppear。它不能按预期工作。似乎是在视图出现后进行边界计算。
我强烈建议不要使用viewDidAppear
。此时您的视图已显示在屏幕上并且对用户可见,您所做的更改可能会导致突然且明显的故障。它也不会帮助您在旋转(等)期间响应视图大小的变化。 Instead, use viewWillLayoutSubviews
(披露:链接到我的答案)这是 iOS 处理此问题的方式。
viewDidAppear 如果用户在它出现后进行旋转将不起作用。正确答案来自@Benjohn viewWillLayoutSubviews 将在您旋转或不旋转时调用它【参考方案3】:
我一直用:
CGSize sizeOfScreen = [[UIScreen mainScreen] bounds].size;
获取屏幕的大小,并且:
CGSize sizeOfView = self.view.bounds.size;
获取view
的大小。我刚刚在 viewDidLoad 上对其进行了测试,它返回了:
2012-07-17 10:25:46.562 Project[3904:15203] bounds = 768, 1004
这是正确的,因为 CGSize 被定义为 Width, Height。
【讨论】:
这很奇怪。您在 viewDidLoad 中获取大小的事实并不重要,因为视图“已加载”【参考方案4】:这是我在上一个项目中发现的:viewDidLoad后self.view的frame会根据这个屏幕是否有导航栏等进行调整。
因此,也许您想在 viewDidLoad 之后使用该值(可能在 viewWillAppear 或 viewDidAppear 中)或通过减去条形高度来手动调整它。
【讨论】:
我已经在viewDidLoad
方法和之后使用它。在我旋转屏幕之前,边界一直是错误的。手动减法是不好的解决方案,因为应用程序针对所有设备和方向..
你会在viewDidLoad之后使用它吗?就我而言,viewDidAppear 将返回正确的大小。【参考方案5】:
我遇到了这个问题,发现在viewDidAppear
中设置边界可以满足我的需要。
【讨论】:
【参考方案6】:如果您想在ViewDidLoad
方法中获得正确的视图边界,您可以使用 Dispatch async with delay 。请参阅下面的示例:
DispatchQueue.main.asyncAfter(deadline: .now() + 1)
// Get your bounds here
【讨论】:
以上是关于获取 UIViewController 视图的正确边界的主要内容,如果未能解决你的问题,请参考以下文章
使用 UIViewControllerRepresentable 时 UIViewController 子视图的大小/布局不正确(没有情节提要)
在单击按钮时重置/重新初始化/重新启动 UIViewController 视图的最简单(正确)方法是啥?
UIView:将 UIViewController 的视图添加为子视图并将其删除
从 UIView 的子视图获取 UINavController?
如何在 iOS7 中将 UIViewController 方向更改为 UIInterfaceOrientationLandscapeLeft 以获取一个视图