当使用自动布局约束时,我如何获取视图的当前宽度和高度?

116
我不是在谈论frame属性,因为从那里你只能获取视图在xib中的大小。我谈论的是当视图由于其约束而被调整大小时(可能是在旋转后或响应事件时),是否有一种方法可以获取其当前的宽度和高度?
我尝试遍历其约束来查找宽度和高度约束,但这不太干净,并且在存在内在约束时会失败(因为我无法区分两者)。此外,这仅适用于它们实际具有宽度和高度约束的情况,如果它们依靠其他约束调整大小,则没有宽度和高度约束。
为什么对我来说这样很困难。ARG!

我本以为视图在任何特定时间的边界都保持其当前的宽度和高度。这就是在布局过程中被调整的内容。 - Phillip Mills
嗯,我从来没有想过要检查边界。我现在会去做的。 - yuf
5个回答

255
答案是[view layoutIfNeeded]
原因如下:
您仍然可以通过检查view.bounds.size.widthview.bounds.size.height(或者等效的frame,除非您正在操作view.transform)来获取视图的当前宽度和高度。
如果您想要现有约束所暗示的宽度和高度,则答案不是手动检查约束,因为这将要求您重新实现自动布局系统的整个约束解决逻辑以解释这些约束。相反,您应该只需请求自动布局更新该布局,以便它解决约束并使用正确的解决方案更新view.bounds的值,然后再检查view.bounds。
如何请求自动布局更新布局?如果要求自动布局在下一个运行循环中更新布局,请调用[view setNeedsLayout]
但是,如果您希望它立即更新布局,以便在当前函数内或在运行循环之前的另一点立即访问新的边界值,则需要调用[view setNeedsLayout][view layoutIfNeeded]
您提出了第二个问题:“如果我没有直接引用,如何更改高度/宽度约束?”

如果您在IB中创建了约束,则最好的解决方案是在视图控制器或视图中创建IBOutlet,以便您可以直接引用它。如果您在代码中创建了约束,则应在创建它时将其保留在内部弱属性中。如果其他人创建了约束,则需要通过检查视图上的view.constraints属性(可能是整个视图层次结构),并实现查找关键NSLayoutConstraint的逻辑来找到它。这可能是错误的做法,因为这也有效地要求您确定哪个特定的约束确定了边界大小,当没有保证有一个简单的答案时。最终的边界值可以是多个约束、多个优先级等非常复杂的系统的解,因此没有单个约束是最终值的“原因”。


16
一个优秀的答案,而且解释得很好。在我抓狂了一两个小时之后,拯救了我。 - Ben Kreeger
3
layoutIfNeeded功能强大,可以立即获取视图的frame。但是,它也会强制渲染在其上调用的视图子树中所有视图的约束条件。例如,如果您正在以编程方式添加视图,并在递归例程中对它们全部调用layoutIfNeeded,则可能会发现您的视图层次结构渲染非常缓慢。(我是通过吃亏才学会这一点的)正如优秀答案中提到的,'setNeedsLayout`更高效,将在下一次布局传递时使frame可用,理想情况下约为1/60秒。 - shmim
1
我知道这很老,但你在哪里调用这个方法?在 initWithCoder 中吗? - MayNotBe
4
为什么要强制布局以获取边界?这似乎效率低下,而且在接口复杂的情况下,这可能会很昂贵。相反,可以从viewDidLayoutSubviews甚至是viewWillLayoutSubviews中访问最新的边界,并让Cocoa处理自己的布局时间。如果需要访问该值或避免多次调用,则设置一个属性或标志。 - SmileBot
1
是的,只有在需要在运行循环之前访问自动布局计算值的情况下(例如,在当前函数调用的范围内),才需要强制布局。 - algal
显示剩余10条评论

8

我曾经遇到过一个类似的问题,需要给一个UITableView添加上下边框,并且这个UITableView根据在UIStoryboard中设置的约束进行自适应。我可以使用- (void)viewDidLayoutSubviews来访问已更新的约束。这对于您不需要对视图进行子类化并覆盖其布局方法非常有用。

/*** SET TOP AND BOTTOM BORDERS ON TABLE VIEW ***/
- (void)addBorders
{
    CALayer *topBorder           = [CALayer layer];
    topBorder.frame              = CGRectMake(0.0f, self.tableView.frame.origin.y, 320.0f, 0.5f);
    topBorder.backgroundColor    = [UIColor redColor].CGColor;

    CALayer *bottomBorder        = [CALayer layer];
    bottomBorder.frame           = CGRectMake(0.0f, (self.tableView.frame.origin.y + self.tableView.frame.size.height), 320.0f, 0.5f);
    bottomBorder.backgroundColor = [UIColor redColor].CGColor;

    [self.view.layer addSublayer:topBorder];
    [self.view.layer addSublayer:bottomBorder];
}

/*** GET AUTORESIZED FRAME DIMENSIONS ***/
- (void)viewDidLayoutSubviews{
    [self addBorders];
}

如果不从viewDidLayoutSubview方法中调用该方法,则只会正确绘制顶部边框,因为底部边框可能在屏幕外。


3
如果viewDidLayoutSubviews被调用多次,你会创建大量的图层... 在添加另一个图层之前,你需要添加一些存在性检查。 - Kalzem
在注释中提到,你应该在任何其他操作之前覆盖调用超类上的此方法:[super viewDidLayoutSubviews]; - Alejandro Iván

5
如果仍然遇到这样的问题,尤其是与TableviewCell有关的问题,请重写以下方法:
-(void)layoutSubviews
{
//your code here like drawing a shadow
}

在UITableViewCell或UICollectionViewCell的情况下,创建单元格的子类并覆盖相同的方法:
-(void)layoutSubviews
{
//your code here like drawing a shadow
}

这是我能够获取视图真实最终大小的唯一方法。 - Benoit Jadinon
这是我能够获取任何受限视图的最终大小的唯一方法,因为之前我尝试在awakeFromNib中获取它们,但此时子视图还没有实际调整大小的时间。谢谢! - Azin Mehrnoosh

3

使用 -(void)viewWillAppear:(BOOL)animated 并调用[self.view layoutIfNeeded]; - 我已经尝试过,它可以工作。

因为如果你使用-(void)viewDidLayoutSubviews,它肯定会工作,但是每当UI需要更新/更改时,此方法都会被调用。这将变得难以管理。最好使用一个bool变量来避免这样的调用循环。记住,如果视图没有被重新分配,viewWillAppear也会被调用。


2

框架仍然有效。最终,视图使用其框架属性来布局自己。它基于所有约束计算该框架。这些约束仅用于初始布局(以及在像旋转后调用layoutSubviews的视图上的任何时间)。之后,位置信息在框架属性中。您有其他观察结果吗?


1
我看到的情况与之前不同。即使直接更改宽度/高度约束(通过更改它们的常数),框架值也不会更改。我在xib中有一个IBOutlet指向它们。 - yuf
我的问题很独特,因为我更改了约束条件,然后需要在同一个函数中使用框架。调用setNeedsLayout不会立即更新它。我猜我需要先等待布局,然后再使用框架。我的第二个问题是,如果我没有直接引用,如何更改高度/宽度约束? - yuf
1
你应该使用dispatch_async,它可以让你的代码延迟到下一帧执行。至于第二部分...我不知道...你需要遍历所有的约束并找出哪一个适用...IBOutlets更容易处理。 - borrrden
对于IBOutlets来说是正确的,但我想在UIView的一个分类中编写一个函数,而我没有IB可供使用。 - yuf

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