ios10: viewDidLoad中frame的宽度/高度未正确初始化

41

自从升级到XCode8 GM和iOS10以后,我通过Interface Builder创建的所有视图都不会在预期的时间内正确初始化。这意味着在viewDidLoad、cellForRowAtIndexPath、viewWillAppear等方法中,每个视图的frame大小都被设置为{1000,1000}。它们似乎在某个时候得到了纠正,但是太晚了。

首先遇到的问题是常见的圆角四边形效果在所有视图上都失败了:

view.layer.cornerRadius = view.frame.size.width/2

依赖帧大小来进行代码计算的任何内容,都会出现进一步的问题。

cellForRowAtIndexPath 

在初始表格显示时,cellForRowAtIndexPath中的框架大小失败,但一旦滚动它就能正常工作。 willDisplayCell:forRowAtIndexPath也没有正确的框架大小。

我已经硬编码了一些值,但很明显这是非常糟糕的代码实践,而且在我的项目中数量相当多。

有没有办法或地方可以获取正确的框架大小?

编辑

我发现使用高度/宽度约束而不是框架宽度高度更可靠。虽然这可能增加需要链接项高度/宽度约束的许多新IBOutlets的开销。

现在,我创建了一个UIView类别,让我直接访问视图的高度/宽度约束,而无需IBOutlets。对于最小使用情况,小循环不会有太大问题。显然,对于尚未创建宽度/高度约束的IB项,结果不能保证为0,甚至更差。

-viewDidLoad似乎具有正确的框架大小,但是如果您在此处进行修改,通常会导致UI的视觉变化。

UIView+WidthHeightConstraints.h

@interface UIView (WidthHeightConstraints)

-(NSLayoutConstraint*)widthConstraint;
-(NSLayoutConstraint*)heightConstraint;
-(NSLayoutConstraint*)constraintForAttribute:(NSLayoutAttribute)attribute;

@end

UIView+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似乎会自动添加几个额外的高度/宽度约束副本,这些约束副本属于NSContentSizeLayoutConstraint类型,实际上与普通约束不同。NSContentSizeLayoutConstraint也是一个私有类,所以我无法使用isKindOfClass过滤它们。我还没有找到另一种有效测试它们的方法。这很烦人。


1
你尝试过重写viewDidLayoutSubviews吗?我曾经遇到过类似的问题,基于宽度约束制作圆形UIImageView。我发现设备的正确变化常量直到viewDidLayoutSubviews才被应用。也许这篇文章可以帮助你。我不确定为什么/如何在Xcode 8/iOS 10中更改了这个。 - Davey Johnson
7个回答

30

你描述的最常见问题仅出现在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
}

1
这并没有解决任何问题。它只是改变了布局的时间,如果不是绝对必要的话,应该避免这样做,因为它会很慢。iOS 10 比其他 iOS 版本晚执行此操作可能有一个很好的原因,你很可能最终会多做一条不必要的渲染路径。 - Michael Ochs
UIView动画怎么样?https://dev59.com/kN7ws4cB2Jgan1zn_vbd#35403833 这里你别无选择。 - pedrouan
5
我的视图是UITableViewCell的子视图,在cellForRowAtIndexPath中调用cell.layoutIfNeeded() 解决了问题。在这种情况下,self.view.layoutIfNeeded并没有起作用。 - DivideByZer0
只是一个小提醒,当你重写这个方法时,应该有super.viewDidLayoutSubviews()。 - Nigel Flack

16
我们为类似问题创建了一个雷达(28342777),并收到以下回复:
“感谢您报告此问题。我们是否可以获得有关个人资料图像视图的更多信息?在Xcode 8中,完全约束但未错位的视图不再保存框架以最小化差异并支持在IB中自动更新框架。在运行时,这些视图将使用占位符大小1000x1000进行解码,但会在第一次布局后解决。图像是否可以在初始布局之前分配,如果在第一次布局后将图像分配给图像视图,是否可以解决此问题?请发送一个样本以帮助我们进一步分析。谢谢!”
目前,我们已向他们提供了示例项目。我的观察:
- 我们遇到的问题通常出现在从Xcode 7.x转换到Xcode 8.x的XIB中。 - 如果我们故意打破XIB中的约束,则viewDidLoad将获得预期的高度和宽度,而不是1000x1000。 - 对于我们来说,这是一个UIImageView,在其中我们应用了一些图层,使其成为圆形,并使用masksToBounds。如果我们将masksToBounds = NO,则一切都正常。
虽然苹果声称这将成为Xcode 8的标准,即视图将设置为1000x1000,但行为似乎并不一致。
希望这可以帮到您。

你找到任何解决办法,使图像变成圆形了吗? - JVS
我遇到了完全相同的问题。 - Nevin Chen
@JVS,如我上面所提到的,通过有意地将视图(图像视图或任何其他控件)放错位置来打破约束,它应该可以工作,并且您仍然可以使用masksToBounds = Yes。 - Bhavik Bhagat
@BhavikBhagat 如果你将分层代码移到单元格的 -layoutSubviews 方法中,那么一切都应该正常工作。你关于 masksToBounds 的提示让我找到了正确的方向。 - TylerJames

6
我遇到了相同的问题并尝试按照上面的建议解决,但未能成功。这似乎是Apple需要解决的一个错误。我最终通过将我的XIB文档保存回Xcode 7.x格式并将UI还原回正常状态来找到了解决方案。在Apple发布修复程序之前,我不想浪费时间去破解它。 enter image description here enter image description here

我同意你的观点,实际上加一。但是如果你切换到Xcode 7.x,你将无法从你的storyboard或xib文件中更改任何内容。 - Alessandro Ornano

2

在布局视图的时间上永远不能依赖。如果以前这样做起作用了,那只是纯属运气。UIKit中几乎没有任何关于此的保证。如果您依赖于某些东西来适应视图的大小,则正确的做法是在该视图中覆盖layoutSubviews并在其中调整相关内容。

即使您的视图已完全呈现在屏幕上,仍然有许多条件可能导致视图的大小发生变化。例如:双倍高度状态栏,在iPad上进行多任务处理,设备旋转,仅举几例。因此,按特定时间点进行框架相关布局更改从来都不是一个好主意。


2
我遇到了完全相同的问题。我有自定义的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;
} 

完成这些步骤后,图像应该可以正确加载,您的分层代码也应该可以正常工作。


2
这样做怎么样:
- (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;
}

是的!虽然我会在isKindOfClass行之前添加一个“!”,因为我不希望奇怪的NSContentSizeLayoutConstraint成为返回的约束。但是这个过滤器对于私有类类型非常有效,并使类别更加准确。 - Miro
我的错!这解决了我的问题。至少在苹果发布补丁之前是这样的。 - 942v

0

在您的自动布局框中,只需更新框架输入图像描述


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