如何在模态视图子视图布局约束中正确使用视图框架尺寸?
问题
为模态视图控制器的子视图设置尺寸时,尝试用正确的frame值模拟视口百分比布局,更倾向用布局约束而非显式frame值,但遇到以下问题:
- 在
viewWillAppear及更早的生命周期方法中,模态视图控制器的frame值未正确设置,仅能在viewWillLayoutSubviews和viewDidAppear中获取正确frame值,但这两个方法存在使用难点:viewWillLayoutSubviews会被多次调用,返回中间frame值,重复设置相同约束会触发Auto Layout约束冲突错误:// viewWillLayoutSubviews返回的多个frame值 {{0, 0}, {393, 783}} {{0, 0}, {393, 283.66666666666663}}viewDidAppear仅调用一次,能获取最终目标frame值,但此时视图已显示在屏幕上,设置约束时会短暂显示未约束布局,再切换到约束后的布局。
临时解决方案
在viewWillLayoutSubviews中先停用旧约束再激活新约束,避免冲突。通过强引用变量保存原约束实现,代码如下:
// 激活约束的工具函数 void setConstraints (UIView* view, NSLayoutAnchor *leading, NSLayoutAnchor *trailing, NSLayoutAnchor *top, NSLayoutAnchor *bottom, CGFloat cLeading, CGFloat cTrailing, CGFloat cTop, CGFloat cBottom, NSArray<NSLayoutConstraint*>* __strong *constraints) { NSArray *newConstraints = @[ [view.leadingAnchor constraintEqualToAnchor:leading constant:cLeading], [view.trailingAnchor constraintEqualToAnchor:trailing constant:cTrailing], [view.topAnchor constraintEqualToAnchor:top constant:cTop], [view.bottomAnchor constraintEqualToAnchor:bottom constant:cBottom] ]; [NSLayoutConstraint deactivateConstraints:*constraints]; [NSLayoutConstraint activateConstraints:newConstraints]; *constraints = newConstraints; } // 获取正确frame值的UIViewController钩子方法 - (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; [self.recordingView setConstraints]; } // 模态视图控制器基于layoutMarginsGuide和当前modal frame值设置自动布局约束 - (void) setConstraints { UILayoutGuide *guide = self.layoutMarginsGuide; CGFloat width = self.frame.size.width; CGFloat height = self.frame.size.height; setConstraints(self.textField, guide.leadingAnchor, guide.trailingAnchor, guide.topAnchor, guide.topAnchor, 0.05*width, -0.05*width, 0.0, 0.2*height, &textFieldConstraints); setConstraints(self.progressBar, guide.leadingAnchor, guide.trailingAnchor, self.textField.bottomAnchor, self.textField.bottomAnchor, 0.05*width, -0.05*width, 0.08*height, 0.1*height, &progressBarConstraints); }
疑问
是否有更优的实现方式?或者是否本就不应该用frame值来实现类似CSS的视口宽高百分比布局?
最优实现方案
完全不需要依赖frame值来实现视口百分比布局,Auto Layout本身就支持基于父视图(或布局guide)的百分比约束,这才是符合Auto Layout设计理念的做法,能彻底解决你遇到的生命周期和约束冲突问题。
具体实现方式
直接使用NSLayoutConstraint的constraintEqualToAnchor:multiplier:方法,或利用布局锚点的链式API,基于父视图尺寸设置百分比约束,无需等待frame值:
示例代码
- (void)setupConstraints { // 禁止视图自动转换frame为约束 self.textField.translatesAutoresizingMaskIntoConstraints = NO; self.progressBar.translatesAutoresizingMaskIntoConstraints = NO; UILayoutGuide *guide = self.layoutMarginsGuide; // textField约束:左右边距为父视图宽度的5%,高度为父视图高度的20%,顶部对齐布局guide [NSLayoutConstraint activateConstraints:@[ [self.textField.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor constant:0], [self.textField.trailingAnchor constraintEqualToAnchor:guide.trailingAnchor constant:0], [self.textField.topAnchor constraintEqualToAnchor:guide.topAnchor constant:0], // 宽度占布局guide宽度的90%(左右各留5%) [self.textField.widthAnchor constraintEqualToAnchor:guide.widthAnchor multiplier:0.9], // 高度占父视图高度的20% [self.textField.heightAnchor constraintEqualToAnchor:self.view.heightAnchor multiplier:0.2] ]]; // progressBar约束:左右边距为父视图宽度的5%,高度为父视图高度的2%,顶部距离textField底部为父视图高度的8% [NSLayoutConstraint activateConstraints:@[ [self.progressBar.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor constant:0], [self.progressBar.trailingAnchor constraintEqualToAnchor:guide.trailingAnchor constant:0], // 顶部间距占父视图高度的8% [self.progressBar.topAnchor constraintEqualToAnchor:self.textField.bottomAnchor constant:0], [self.progressBar.topAnchor constraintEqualToAnchor:self.view.heightAnchor multiplier:0.08 constant:0], // 宽度占布局guide宽度的90% [self.progressBar.widthAnchor constraintEqualToAnchor:guide.widthAnchor multiplier:0.9], // 高度占父视图高度的2% [self.progressBar.heightAnchor constraintEqualToAnchor:self.view.heightAnchor multiplier:0.02] ]]; }
关键说明
- 一次性设置约束:在
viewDidLoad中调用setupConstraints即可,Auto Layout会自动根据父视图尺寸变化更新子视图布局,无需在布局生命周期方法中重复设置。 - 避免约束冲突:约束仅设置一次,不存在重复激活/停用操作,彻底解决约束冲突问题。
- 无布局闪烁:约束在视图显示前就已设置完成,不会出现
viewDidAppear中设置约束导致的布局闪烁问题。
补充优化
如果需要基于layoutMarginsGuide的百分比边距,直接用multiplier调整即可,比如子视图宽度设为布局guide宽度的0.9倍,等价于左右各留5%的边距,无需计算具体constant值。
若适配iOS 11及以上版本,可使用safeAreaLayoutGuide替代layoutMarginsGuide,适配刘海屏等异形屏的安全区域。
内容的提问来源于stack exchange,提问作者enell
相关产品推荐
相关产品推荐

