我有一个非常简单的UIViewController
,我正在尝试更好地理解约束,自动布局和框架。视图控制器有两个子视图:两个都是UIView
s,它们可以并排放置,也可以根据设备方向坐在上下。在每个UIView
中,存在一个应该在其超级视图中居中的单个标签。
旋转设备后,UIView
s正确更新。我正在计算他们的框架尺寸和起源。但是,标签不会保持居中,并且不遵守故事板中定义的约束。
以下是显示此问题的屏幕截图。如果我注释掉viewDidLayoutSubviews
方法,则标签完全居中(但UIView
s的大小不正确)。我意识到我可以手动调整每个标签的框架,但我正在寻找一种方法,让他们在新调整大小的超级视图中尊重他们的约束。
这是代码:
#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
答案 0 :(得分:12)
通常,将帧与自动布局混合是一个坏主意。 (例外是一个视图层次结构,它使用包含不包含视图的约束,然后不使用该点的任何约束[和其他警告])。一个大问题是约束系统通常不会从setFrame中获取任何信息。
另一个经验法则是在约束系统之前计算setFrame和传统布局树。这可能看起来与第一部分相反,但请记住1)在传统的布局树中,视图布置了它们的子视图,然后在它们上面调用layoutSubviews,因此每个人的超视图框架在它自己放置之前设置但是2)在约束系统,它试图从子视图,自下而上计算超视图帧。但在获取信息后,每个子视图报告信息,布局工作自上而下完成。
离开你的地方?你需要以编程方式设置它是正确的。在IB中没有办法表明你应该从上到下切换到左右。以下是您可以这样做的方法:
识别(或创建)链接右/底部的两个约束 查看左侧和顶部的内容。您可能希望使用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。这是调用超类私有实现来为此视图进行计算,而不是视图层次结构中此视图的超级视图,尽管事实上下一步将在树的更上一层。