ios10: viewDidLoad 框架 width/height 未正确初始化
ios10: viewDidLoad frame width/height not initialized correctly
自从升级到 XCode8 GM 和 ios10,我通过 Interface Builder 创建的所有视图都没有正确初始化,直到比预期晚得多。这意味着在 viewDidLoad、cellForRowAtIndexPath、viewWillAppear 等中,每个视图的帧大小都设置为 {1000,1000}。在某些时候他们似乎纠正了,但为时已晚。
遇到的第一个问题是常见的圆角全面失败:
view.layer.cornerRadius = view.frame.size.width/2
在代码中依赖帧大小进行计算的任何内容都会出现更多问题。
cellForRowAtIndexPath
对于 cellForRowAtIndexPath,帧大小在初始 table 显示时失败,但一旦滚动它就可以正常工作。 willDisplayCell:forRowAtIndexPath 也没有正确的帧大小。
我已经硬编码了一些值,但显然这是非常糟糕的代码实践,而且在我的项目中有很多。
有没有办法或地方获得正确的帧大小?
编辑
我发现使用 height/width 约束而不是框架宽度高度更可靠。不过,这可能会增加需要大量新 IBOutlets 的开销 link height/width 对项目的限制。
现在我已经创建了一个 UIView 类别,它允许我在没有 IBOutlet 的情况下直接访问 View 的 height/width 约束。对于最少的使用,小循环应该没什么大不了的。显然,在没有创建 width/height 约束的情况下,无法保证 IB 项目的结果。对于常量,可能 returns 0 最多,或者更糟。此外,如果您没有 height/width 约束并且您的视图是根据 leading/trailing 约束动态调整大小的,这将不起作用。
-viewDidLoad 似乎具有正确的帧大小,但如果您在此处进行修改,通常会导致 UI 的视觉变化。
UI查看+WidthHeightConstraints.h
@interface UIView (WidthHeightConstraints)
-(NSLayoutConstraint*)widthConstraint;
-(NSLayoutConstraint*)heightConstraint;
-(NSLayoutConstraint*)constraintForAttribute:(NSLayoutAttribute)attribute;
@end
UI查看+WidthHeightConstraints.m
#import "UIView+WidthHeightConstraints.h"
@implementation UIView (WidthHeightConstraints)
-(NSLayoutConstraint*)widthConstraint{
return [self constraintForAttribute:NSLayoutAttributeWidth];
}
-(NSLayoutConstraint*)heightConstraint {
return [self constraintForAttribute:NSLayoutAttributeHeight];
}
-(NSLayoutConstraint*)constraintForAttribute:(NSLayoutAttribute)attribute {
NSLayoutConstraint *targetConstraint = nil;
for (NSLayoutConstraint *constraint in self.constraints) {
if (constraint.firstAttribute == attribute) {
targetConstraint = constraint;
break;
}
}
return targetConstraint;
}
@end
编辑 2
上述类别仅证明部分有效。主要是因为 ios 似乎自动添加了几个额外的 height/width 约束重复项,它们属于 NSContentSizeLayoutConstraint 类型,实际上与普通约束的大小不同。 NSContentSizeLayoutConstraint 也是私有的 class 所以我不能做 isKindOfClass 来过滤掉那些。我还没有找到另一种方法来有效地测试这些。这很烦人。
这样做怎么样:
- (NSLayoutConstraint*)widthConstraint{
return [self constraintForAttribute:NSLayoutAttributeWidth];
}
- (NSLayoutConstraint*)heightConstraint {
return [self constraintForAttribute:NSLayoutAttributeHeight];
}
- (NSLayoutConstraint*)constraintForAttribute:(NSLayoutAttribute)attribute {
NSLayoutConstraint *targetConstraint = nil;
for (NSLayoutConstraint *constraint in self.constraints) {
//NSLog(@"constraint: %@", constraint);
if (![constraint isKindOfClass:NSClassFromString(@"NSContentSizeLayoutConstraint")]) {
if (constraint.firstAttribute == attribute) {
targetConstraint = constraint;
break;
}
}
}
return targetConstraint;
}
您描述的最常见问题仅出现在 iOS 10 中,可以通过添加此行(如有必要)来解决:
self.view.layoutIfNeeded()
就在代码上方,负责更改约束,layer.cornerRadius等
或
将与帧/图层相关的代码放入 viewDidLayoutSubviews()
方法中:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
view.layer.cornerRadius = self.myView.frame.size.width/2
view.clipsToBounds = true
... etc
}
您永远不应依赖视图布局的时间。如果这之前对你有用,那么纯属运气。在 UIKit 中对此几乎没有任何保证。如果你依赖于适应你的视图大小的东西,正确的做法是在那个视图中覆盖 layoutSubviews
并在那里调整你的东西。
即使您的视图完全呈现在屏幕上之后,仍然有很多情况会导致视图的大小发生变化。例如:双倍高度状态栏、iPad 上的多任务处理、设备旋转,仅举几例。因此,在特定时间点进行与框架相关的布局更改绝不是一个好主意。
我们为类似的问题创建了一个雷达(28342777(标记为重复 28221021 但打开)),我们得到的回复如下:
"Thank you for reporting the issue. Could we get more information about the profile image view? In Xcode 8, a fully constraint, non-misplaced view no longer saves out a frame to minimize diffs and support automatically update frames in IB. At runtime, these views get decoded with a placeholder size of 1000x1000, but are resolved after first layout. Could the image be assigned before initial layout, and would assigning the image to the image view after first layout address this case? Please send a sample to help us further analyze. thanks!"
目前我们已经为他们提供了样例工程。我的观察:
- 我们曾经遇到过从 Xcode 7.x 转换为 Xcode 8.x
的 XIB 的问题
- 如果我们故意打破 XIB 中的约束,那么 viewDidLoad 将获得预期的高度和宽度,而不是 1000x1000。
- 对我们来说,它是一个 UIImageView,我们在其上应用了一些分层以使其成为圆形并使用了 masksToBounds。如果我们设置 masksToBounds = NO 那么我们一切正常。
虽然 Apple 声称它将成为 Xcode 8 的标准,视图将设置为 1000x1000,但行为似乎并不一致。
希望这对您有所帮助。
我也遇到了同样的问题,参考了上面的建议尝试解决,没有成功。
看来应该是苹果要解决的bug。我终于通过更改将我的 XIB 文档保存回 Xcode 7.x 格式和我的 UI 恢复正常来找到解决方案。
在 Apple 发布修复程序之前,我不想花时间破解它。
我遇到了完全相同的问题。我有自定义的 UITableViewCell 子类,并使用 clipsToBounds = YES
和 self.iconView.layer.cornerRadius = self.iconView.frame.size.width/2
给自己一个圆形图像。尝试从 cellForRowAtIndexPath
和 willDisplayCell
调用我的单元格配置方法,但均无效。
这是有效的:
将分层代码移动到单元格的 -layoutSubviews
方法中,如下所示:
-(void)layoutSubviews {
[super layoutSubviews];
self.iconView.clipsToBounds = YES;
self.iconView.layer.cornerRadius = self.iconView.frame.size.width/2;
}
在此之后,图像应该可以正确加载,您的分层代码也应该可以正常工作。
仅更新自动布局框中的框架。
自从升级到 XCode8 GM 和 ios10,我通过 Interface Builder 创建的所有视图都没有正确初始化,直到比预期晚得多。这意味着在 viewDidLoad、cellForRowAtIndexPath、viewWillAppear 等中,每个视图的帧大小都设置为 {1000,1000}。在某些时候他们似乎纠正了,但为时已晚。
遇到的第一个问题是常见的圆角全面失败:
view.layer.cornerRadius = view.frame.size.width/2
在代码中依赖帧大小进行计算的任何内容都会出现更多问题。
cellForRowAtIndexPath
对于 cellForRowAtIndexPath,帧大小在初始 table 显示时失败,但一旦滚动它就可以正常工作。 willDisplayCell:forRowAtIndexPath 也没有正确的帧大小。
我已经硬编码了一些值,但显然这是非常糟糕的代码实践,而且在我的项目中有很多。
有没有办法或地方获得正确的帧大小?
编辑
我发现使用 height/width 约束而不是框架宽度高度更可靠。不过,这可能会增加需要大量新 IBOutlets 的开销 link height/width 对项目的限制。
现在我已经创建了一个 UIView 类别,它允许我在没有 IBOutlet 的情况下直接访问 View 的 height/width 约束。对于最少的使用,小循环应该没什么大不了的。显然,在没有创建 width/height 约束的情况下,无法保证 IB 项目的结果。对于常量,可能 returns 0 最多,或者更糟。此外,如果您没有 height/width 约束并且您的视图是根据 leading/trailing 约束动态调整大小的,这将不起作用。
-viewDidLoad 似乎具有正确的帧大小,但如果您在此处进行修改,通常会导致 UI 的视觉变化。
UI查看+WidthHeightConstraints.h
@interface UIView (WidthHeightConstraints)
-(NSLayoutConstraint*)widthConstraint;
-(NSLayoutConstraint*)heightConstraint;
-(NSLayoutConstraint*)constraintForAttribute:(NSLayoutAttribute)attribute;
@end
UI查看+WidthHeightConstraints.m
#import "UIView+WidthHeightConstraints.h"
@implementation UIView (WidthHeightConstraints)
-(NSLayoutConstraint*)widthConstraint{
return [self constraintForAttribute:NSLayoutAttributeWidth];
}
-(NSLayoutConstraint*)heightConstraint {
return [self constraintForAttribute:NSLayoutAttributeHeight];
}
-(NSLayoutConstraint*)constraintForAttribute:(NSLayoutAttribute)attribute {
NSLayoutConstraint *targetConstraint = nil;
for (NSLayoutConstraint *constraint in self.constraints) {
if (constraint.firstAttribute == attribute) {
targetConstraint = constraint;
break;
}
}
return targetConstraint;
}
@end
编辑 2
上述类别仅证明部分有效。主要是因为 ios 似乎自动添加了几个额外的 height/width 约束重复项,它们属于 NSContentSizeLayoutConstraint 类型,实际上与普通约束的大小不同。 NSContentSizeLayoutConstraint 也是私有的 class 所以我不能做 isKindOfClass 来过滤掉那些。我还没有找到另一种方法来有效地测试这些。这很烦人。
这样做怎么样:
- (NSLayoutConstraint*)widthConstraint{
return [self constraintForAttribute:NSLayoutAttributeWidth];
}
- (NSLayoutConstraint*)heightConstraint {
return [self constraintForAttribute:NSLayoutAttributeHeight];
}
- (NSLayoutConstraint*)constraintForAttribute:(NSLayoutAttribute)attribute {
NSLayoutConstraint *targetConstraint = nil;
for (NSLayoutConstraint *constraint in self.constraints) {
//NSLog(@"constraint: %@", constraint);
if (![constraint isKindOfClass:NSClassFromString(@"NSContentSizeLayoutConstraint")]) {
if (constraint.firstAttribute == attribute) {
targetConstraint = constraint;
break;
}
}
}
return targetConstraint;
}
您描述的最常见问题仅出现在 iOS 10 中,可以通过添加此行(如有必要)来解决:
self.view.layoutIfNeeded()
就在代码上方,负责更改约束,layer.cornerRadius等
或
将与帧/图层相关的代码放入 viewDidLayoutSubviews()
方法中:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
view.layer.cornerRadius = self.myView.frame.size.width/2
view.clipsToBounds = true
... etc
}
您永远不应依赖视图布局的时间。如果这之前对你有用,那么纯属运气。在 UIKit 中对此几乎没有任何保证。如果你依赖于适应你的视图大小的东西,正确的做法是在那个视图中覆盖 layoutSubviews
并在那里调整你的东西。
即使您的视图完全呈现在屏幕上之后,仍然有很多情况会导致视图的大小发生变化。例如:双倍高度状态栏、iPad 上的多任务处理、设备旋转,仅举几例。因此,在特定时间点进行与框架相关的布局更改绝不是一个好主意。
我们为类似的问题创建了一个雷达(28342777(标记为重复 28221021 但打开)),我们得到的回复如下:
"Thank you for reporting the issue. Could we get more information about the profile image view? In Xcode 8, a fully constraint, non-misplaced view no longer saves out a frame to minimize diffs and support automatically update frames in IB. At runtime, these views get decoded with a placeholder size of 1000x1000, but are resolved after first layout. Could the image be assigned before initial layout, and would assigning the image to the image view after first layout address this case? Please send a sample to help us further analyze. thanks!"
目前我们已经为他们提供了样例工程。我的观察:
- 我们曾经遇到过从 Xcode 7.x 转换为 Xcode 8.x 的 XIB 的问题
- 如果我们故意打破 XIB 中的约束,那么 viewDidLoad 将获得预期的高度和宽度,而不是 1000x1000。
- 对我们来说,它是一个 UIImageView,我们在其上应用了一些分层以使其成为圆形并使用了 masksToBounds。如果我们设置 masksToBounds = NO 那么我们一切正常。
虽然 Apple 声称它将成为 Xcode 8 的标准,视图将设置为 1000x1000,但行为似乎并不一致。
希望这对您有所帮助。
我也遇到了同样的问题,参考了上面的建议尝试解决,没有成功。
看来应该是苹果要解决的bug。我终于通过更改将我的 XIB 文档保存回 Xcode 7.x 格式和我的 UI 恢复正常来找到解决方案。
在 Apple 发布修复程序之前,我不想花时间破解它。
我遇到了完全相同的问题。我有自定义的 UITableViewCell 子类,并使用 clipsToBounds = YES
和 self.iconView.layer.cornerRadius = self.iconView.frame.size.width/2
给自己一个圆形图像。尝试从 cellForRowAtIndexPath
和 willDisplayCell
调用我的单元格配置方法,但均无效。
这是有效的:
将分层代码移动到单元格的 -layoutSubviews
方法中,如下所示:
-(void)layoutSubviews {
[super layoutSubviews];
self.iconView.clipsToBounds = YES;
self.iconView.layer.cornerRadius = self.iconView.frame.size.width/2;
}
在此之后,图像应该可以正确加载,您的分层代码也应该可以正常工作。
仅更新自动布局框中的框架。