父视图框架尺寸更改后更新约束
Update constraints after superview frame dimensions change
我有一个非常简单的 UIViewController
,我正在使用它来尝试更好地理解约束、自动布局和框架。视图控制器有两个子视图:两者都是 UIView
,根据设备方向,它们可以并排放置,也可以 top/bottom。在每个 UIView
中,存在一个应该在其父视图中居中的标签。
旋转设备时,UIView
s 会正确更新。我正在计算它们的框架尺寸和来源。但是,标签不会保持居中,并且不遵守故事板中定义的约束。
以下是显示问题的屏幕截图。如果我注释掉 viewDidLayoutSubviews
方法,标签将完全居中(但 UIView
的大小不正确)。我意识到我可以手动调整每个标签的框架,但我正在寻找一种方法让它们在新调整大小的超级视图中尊重它们的约束。
这是代码:
#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 中无法指示您应该从上下切换到左右。方法如下:
- 选择一个旋转并确保设置所有约束
您在界面生成器中想要的方式-例如,每种颜色
视图从超级视图中放置 8 个点(您的间隔视图)。 “明确约束”和
底部的“更新帧”按钮会帮助你,你会想要点击
通常是为了确保同步。
- 非常重要的是,左上角的视图只能连接到
左(领先)和顶部以及右下角的超级视图
仅由右侧(尾部)和底部连接。如果你清除
尺寸设置固定的高度和宽度,这将产生一个
警告。这是正常的,这种情况可以通过设置来解决
“等宽”和“等高”以及必要时第 3 步的一部分。
(请注意,要使值真正相等,常数必须为零。)
在其他情况下,我们必须放置一个约束并将其标记为“占位符”
使编译器静音,如果我们确定我们将填充信息但编译器不知道。
识别(或创建)链接 right/bottom 的两个约束
查看左侧和顶部的某物。您可能想使用 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在向上传递信息的时候会用到这里的constraints,所以我们先修改,然后调用super。这是调用 super class 私有实现来为这个视图做计算,而不是视图层次结构中这个视图的父视图,尽管实际上下一步将在树的更上层。
我有一个非常简单的 UIViewController
,我正在使用它来尝试更好地理解约束、自动布局和框架。视图控制器有两个子视图:两者都是 UIView
,根据设备方向,它们可以并排放置,也可以 top/bottom。在每个 UIView
中,存在一个应该在其父视图中居中的标签。
旋转设备时,UIView
s 会正确更新。我正在计算它们的框架尺寸和来源。但是,标签不会保持居中,并且不遵守故事板中定义的约束。
以下是显示问题的屏幕截图。如果我注释掉 viewDidLayoutSubviews
方法,标签将完全居中(但 UIView
的大小不正确)。我意识到我可以手动调整每个标签的框架,但我正在寻找一种方法让它们在新调整大小的超级视图中尊重它们的约束。
#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 中无法指示您应该从上下切换到左右。方法如下:
- 选择一个旋转并确保设置所有约束 您在界面生成器中想要的方式-例如,每种颜色 视图从超级视图中放置 8 个点(您的间隔视图)。 “明确约束”和 底部的“更新帧”按钮会帮助你,你会想要点击 通常是为了确保同步。
- 非常重要的是,左上角的视图只能连接到 左(领先)和顶部以及右下角的超级视图 仅由右侧(尾部)和底部连接。如果你清除 尺寸设置固定的高度和宽度,这将产生一个 警告。这是正常的,这种情况可以通过设置来解决 “等宽”和“等高”以及必要时第 3 步的一部分。 (请注意,要使值真正相等,常数必须为零。) 在其他情况下,我们必须放置一个约束并将其标记为“占位符” 使编译器静音,如果我们确定我们将填充信息但编译器不知道。
识别(或创建)链接 right/bottom 的两个约束 查看左侧和顶部的某物。您可能想使用 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在向上传递信息的时候会用到这里的constraints,所以我们先修改,然后调用super。这是调用 super class 私有实现来为这个视图做计算,而不是视图层次结构中这个视图的父视图,尽管实际上下一步将在树的更上层。