超视图框架尺寸更改后更新约束
我有一个非常简单的UIViewController
,我正在尝试更好地理解约束,自动布局和框架。 视图控制器有两个子视图:两个都是UIView
,它们可以并排放置,也可以根据设备方向并排放置在顶部/底部。 在每个UIView
,存在一个应该在其superview中居中的单个标签。
旋转设备时, UIView
正确更新。 我正在计算他们的框架尺寸和起源。 但是,标签不会保持居中,并且不遵守故事板中定义的约束。
以下是显示问题的屏幕截图。 如果我注释掉viewDidLayoutSubviews
方法,标签完全居中(但是UIView
的大小不正确)。 我意识到我可以手动调整每个标签的框架,但我正在寻找一种方法,让他们在新resize的超级视图中尊重他们的约束。 这是代码:
#import "ViewController.h" @interface ViewController () @property (nonatomic) CGFloat spacer; @end @implementation ViewController @synthesize topLeftView, bottomRightView, topLeftLabel, bottomRightLabel; - (void)viewDidLoad { [super viewDidLoad]; topLeftLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; bottomRightLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.spacer = 8.0f; } - (void)viewDidLayoutSubviews { if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) { [self setupTopLeftForLandscape]; [self setupBottomRightForLandscape]; } else { [self setupTopLeftForPortrait]; [self setupBottomRightForPortrait]; } } - (void) setupTopLeftForPortrait { CGRect frame = topLeftView.frame; frame.origin.x = self.spacer; frame.origin.y = self.spacer; frame.size.width = self.view.frame.size.width - 2*self.spacer; frame.size.height = (self.view.frame.size.height - 3*self.spacer) * 0.5; [topLeftView setFrame:frame]; } - (void) setupBottomRightForPortrait { CGRect frame = bottomRightView.frame; frame.origin.x = self.spacer; frame.origin.y = topLeftView.frame.size.height + 2*self.spacer; frame.size.width = topLeftView.frame.size.width; frame.size.height = topLeftView.frame.size.height; [bottomRightView setFrame:frame]; } - (void) setupTopLeftForLandscape { CGRect frame = topLeftView.frame; frame.origin.x = self.spacer; frame.origin.y = self.spacer; frame.size.width = (self.view.frame.size.width - 3*self.spacer) * 0.5; frame.size.height = self.view.frame.size.height - 2*self.spacer; [topLeftView setFrame:frame]; } - (void) setupBottomRightForLandscape { CGRect frame = bottomRightView.frame; frame.origin.x = self.topLeftView.frame.size.width + 2*self.spacer; frame.origin.y = self.spacer; frame.size.width = topLeftView.frame.size.width; frame.size.height = topLeftView.frame.size.height; [bottomRightView setFrame:frame]; } @end
通常,将帧与自动布局混合是一个坏主意。 (例外是一个视图层次结构,它使用包含不包含视图的约束,然后不使用该点的任何约束[和其他警告])。 一个大问题是约束系统通常不会从setFrame中获取任何信息。
另一个经验法则是在约束系统之前计算setFrame和传统布局树。 这可能看起来与第一部分相反,但请记住1)在传统的布局树中,视图布置了它们的子视图,然后在它们上面调用layoutSubviews,因此每个人的超级视图框架在它自己放置之前设置但是2)在约束系统,它试图从子视图,自下而上计算超视图帧。 但在获取信息后,每个子视图报告信息,布局工作自上而下完成。
定影
那你离开了哪里? 你是正确的,你需要以编程方式设置它。 在IB中没有办法表明你应该从上到下切换到左右。 这是你如何做到这一点:
- 选择一个旋转并确保在“界面”构建器中按照您希望的方式设置所有约束 – 例如,每个彩色视图从superview中放置8个点(您的间隔视图)。 底部的“清除约束”和“更新框架”按钮将帮助您,您将需要经常单击它以确保它是同步的。
- 非常重要的是,左上角视图仅通过左侧(前端)和顶侧连接到超视图,而右下角仅通过右侧(尾部)和底侧连接。 如果清除尺寸设置高度和宽度固定,这将产生警告。 这是正常的,在这种情况下,可以通过设置“相等宽度”和“相等高度”来解决,如果需要,可以通过步骤3的一部分来解决。 (注意,为了使值真正相等,常量必须为零。)在其他情况下,我们必须设置一个约束并将其标记为“占位符”以使编译器静音,如果我们确定我们将填充信息但编译器没有不知道。
-
识别(或创建)将右/底视图链接到左侧和顶部的两个约束。 您可能希望使用IB左侧的对象浏览器。 使用助手编辑器在viewController.h中创建两个出口。 看起来像:
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomViewToTopConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *rightViewToLeftConstraint;
-
在viewController中实现updateConstraints。 这是逻辑的发展方向:
。
-(void)updateViewConstraints { //first remove the constraints [self.view removeConstraints:@[self.rightViewToLeftConstraint, self.bottomViewToTopConstraint]]; if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) { //align the tops equal self.bottomViewToTopConstraint = [NSLayoutConstraint constraintWithItem:self.bottomRightView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.topLeftView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]; //align to the trailing edge by spacer self.rightViewToLeftConstraint = [NSLayoutConstraint constraintWithItem:self.bottomRightView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.topLeftView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:self.spacer]; } else { //portrait //right view atached vertically to the bottom of topLeftView by spacer self.bottomViewToTopConstraint = [NSLayoutConstraint constraintWithItem:self.bottomRightView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.topLeftView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:self.spacer]; //bottom view left edge aligned to left edge of top view self.rightViewToLeftConstraint = [NSLayoutConstraint constraintWithItem:self.bottomRightView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.topLeftView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0]; } [self.view addConstraints:@[self.rightViewToLeftConstraint, self.bottomViewToTopConstraint]]; [super updateViewConstraints];
}
由于在添加约束后不能更改约束(常量除外),我们必须执行此删除 – 添加步骤。 注意IB中的那些也可能是占位符,因为我们每次都删除它们(我们可以先检查)。 我们可以将常量修改为某个偏移值,例如通过spacer + topViewHight + spacer与superview相关。 但这意味着当自动布局计算此视图时,您已根据其他可能已更改的信息做出假设。 交换视图并更改它们相关的因素,这些因素旨在相互关联。
请注意,因为Auto Layout将在传递信息时使用约束,首先我们修改它们,然后我们调用super。 这是调用超类私有实现来为此视图进行计算,而不是视图层次结构中此视图的超级视图,尽管实际上下一步将在树的更上一层。