我能同时在同一个视图上使用setFrame和autolayout吗?

71
我想要给所有按钮添加填充,因此我创建了一个子类UIButton,并在其他更改之间,希望使用setFrame方法添加固定填充。一切都正常,唯独setFrame失效。我进行了检查,发现如果在该视图上取消“使用AutoLayout”选项,则可以使用setFrame,并且它可以工作。有没有什么方法可以避免这种情况?我真的想使用自动布局,因为它可以帮助我的应用程序在iPhone 5和早期设备上看起来不错。但是我还想在我的子类中使用setFrame,以使我的生活变得轻松一些。
总之,我的问题是:我可以在编程上使用autolayout同时调整UIView的框架吗?
5个回答

215

是的,这可以实现。

如果您设置视图的translatesAutoresizingMaskIntoConstraints = YES,则在运行时,对setFrame:的调用会自动转换为基于视图当前autoresizingMask的布局约束。这使您可以将基于框架的布局与基于约束的布局混合使用。

例如,您可以使用自动布局定义视图的所有子视图的布局,但仍然可以调用setFrame:来设置视图本身的大小和位置。从您的角度来看,您正在使用自动布局和直接框架操作进行布局。但实际上,系统正在使用约束来处理所有内容。

但是,使用translatesAutoresizingMaskIntoConstraints有一个重要的注意事项。

当您这样做时,您仍然需要确保这些自动约束可以满足其余的约束

因此,例如,假设已经存在约束来确定您的视图的大小和位置,然后您还设置了translatesAutoresizingMaskIntoConstraints = YES并调用了setFrame:。对setFrame:的调用将在视图上生成新的约束,这可能会与已经存在的约束冲突。

实际上,这种错误经常发生。如果你看到日志消息抱怨冲突的约束条件,其中一个约束条件是NSAutoresizingMaskLayoutConstraint,那么你所看到的就是与自动约束冲突。这是一个容易犯的错误,因为translatesAutoresizingMaskIntoConstraints = YES是默认值,所以如果你在代码中配置约束条件,如果不想要这些自动约束条件,就需要记得将其关闭。

相反地,假设已经存在约束条件来确定视图的大小和位置,但在调用setFrame:之前设置了translatesAutoresizingMaskIntoConstraints = NO。在这种情况下,setFrame:调用将不会产生新的约束条件,因此不会在不同的约束条件之间发生冲突。然而,在这种情况下,仍然存在约束条件和你设置的框架值之间的“冲突”。在下一次自动布局被调用时,它会看到视图上已经存在的约束条件,计算出它们所需的框架值,并将框架设置为所需的值,从而覆盖你手动设置的值。

请查看苹果的Cocoa Auto Layout Guide中的“采用自动布局”一节,以获取更多细节。

2
这个能够工作,但是我在日志中看到了很多警告。有人能建议一下应该如何正确使用这种方法吗?还是我应该忽略这些警告?谢谢。 - Hussain Mansoor
4
如果您看到不满足约束条件的警告,那可能是有意义的。如果在对视图使用 translatedAutoresizingMaskIntoConstraints=true 进行操作时看到这些警告,则很可能已经存在一些约束(可能是在IB中设置的),试图控制该视图的框架。这些旧约束与从自动调整大小掩码自动生成的约束相互作用。在这种情况下,答案是删除这些旧约束。如果您只是为了让IB在设计时保持安静而在IB中添加它们,那么可以将它们变成“占位符”以将其删除。 - algal
@algal,你怎么才能摆脱“旧”的约束条件?让子视图只监听setFrame:的值,这样它们两个就不会冲突了。 - Peter
这个在iOS 10上还能用吗?我试图在自定义容器VC中为一个子VC做这件事,但它不遵循我设置的框架。 - bartzy
1
我注意到,如果你正在这样做,在许多情况下,你将想要从使用自动布局的其他视图的框架中派生出框架的CGRect。在这种情况下,您需要在其他视图完成其自动布局传递之后执行帧计算。在视图控制器中,viewDidLayoutSubviews是这样做的地方。在自定义视图中应该使用layoutSubviews。在两种情况下,您都应该在super调用之后放置您的框架计算。也许将此添加到答案中会很有价值,但我希望这是一个有用的评论! - Benjohn

16

对我来说最容易的事情是将我想要移动/调整大小的视图从其父视图中移除,设置其frame,然后重新添加它。例如,在UITableViewCell子类中使用自定义UILabel

[cell.myLabel removeFromSuperview];
cell.myLabel.frame = someFrameIGenerated;
[cell.contentView addSubview:cell.myLabel];

3
没问题。虽然我觉得有义务告诉你,我最终放弃了这种方法,实际上学会了如何在接口生成器(IB)中使用约束(constraints)。我以前不知道它们的存在,现在制作复杂的布局看起来更加合理了。 - Andrew
是的,我尝试过了,但无法在表头视图上使用约束。也许我应该再试一次。 - Marky
1
通过这样做,您正在消除可能会破坏其他事情的限制。要小心。 - donkey

3

有一个对我有效的方法是为我的约束添加 IBOutlet,然后在视图控制器中更改这些约束的 constant 属性。

例如,要更改视图的 x 坐标,请从该视图的前导约束创建一个到您的控制器的 outlet。换句话说,创建一个 outlet:@IBOutlet var labelXOrigin: NSLayoutConstraint!。然后,当您想要调整 x 位置时,只需执行类似以下操作:

self.labelXOrigin.constant = 20.0 // Or whatever origin you want to set.

2
在自动布局项目中,设置子视图的框架最好的方法是在viewWillLayoutSubviews方法中进行。将属性translatedAutoresizingMaskIntoConstraints = true设置为true,这将在视图出现后或点击按钮后生效,在视图出现后立即生效。

0
如果您的应用程序不支持多个设备方向。
viewDidLoad 中设置您想要调整大小的视图的框架。
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [aView setFrame:CGRectMake(15, 15, 100, 100)];
}

然后在 viewDidLayoutSubviews 方法中:

-(void)viewDidLayoutSubviews
{
    [self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
}

我不确定,但我认为如果您使用这个解决方案,切换一个方向到另一个方向可能会导致问题。


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