超视图框架尺寸更改后更新约束 [英] Update constraints after superview frame dimensions change

查看:79
本文介绍了超视图框架尺寸更改后更新约束的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个非常简单的 UIViewController ,我正在尝试更好地理解约束,自动布局和框架。视图控制器有两个子视图:两者都是 UIView s,它们可以并排放置,也可以根据设备方向并排放置在顶部/底部。在每个 UIView 中,存在一个应该在其superview中居中的单个标签。

I have a very simple UIViewController that I am using to try to better understand constraints, auto layout, and frames. The view controller has two subviews: both are UIViews that are intended to either sit side-by-side or top/bottom depending on the device orientation. Within each UIView, there exists a single label that should be centered within its superview.

旋转设备后, UIView 会正确更新。我正在计算他们的框架尺寸和起源。但是,标签不会保持居中,并且不遵守故事板中定义的约束。

When the device is rotated, the UIViews update correctly. I am calculating their frame dimensions and origins. However, the labels do not stay centered and they do not respect the constraints defined in the storyboard.

以下是显示问题的屏幕截图。如果我注释掉 viewDidLayoutSubviews 方法,则标签完全居中(但是 UIView s不正确尺寸)。我意识到我可以手动调整每个标签的框架,但我正在寻找一种方法,让他们在新调整大小的超级视图中尊重他们的约束。


以下是代码:

Here are screenshots to show the issue. If I comment out the viewDidLayoutSubviews method, the labels are perfectly centered (but then the UIViews are not of the correct size). I realize that I could manually adjust the frame for each of the labels, but I am looking for a way to make them respect their constraints within the newly resized superviews. Here is the code:

#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中获取任何信息。

Generally it’s a bad idea to mix frames with Auto Layout. (The exception is a view hierarchy that uses constraints containing a view that doesn’t, which then doesn’t use any constraints from that point down [and additional caveats]). One big problem is the constraint system generally won’t get any information from setFrame.

另一个经验法则是setFrame和传统布局树是在约束系统之前计算的。这可能看起来与第一部分相反,但请记住1)在传统的布局树中,视图布置了它们的子视图,然后在它们上面调用layoutSubviews,因此每个人的超级视图框架在它自己放置之前设置但是2)在约束系统,它试图从子视图,自下而上计算超视图帧。但在获取信息后,每个子视图报告信息,布局工作自上而下完成。

Another rule of thumb is that setFrame and the traditional layout tree is calculated before the constraint system. This may seem counter intuitive with the first part, but remember that 1) in the traditional layout tree the views lay out their subviews and then call layoutSubviews on them, so each one’s superview frame is set before it lays itself out but 2) in the constraint system, it tries to calculate the superview frame from the subviews, bottom-up. But after getting the information bottom up, each subview reporting up info, the layout work is done top-down.

这会让你离开?你是正确的,你需要以编程方式设置它。在IB中没有办法表明你应该从上到下切换到左右。以下是如何做到这一点:

Where does that leave you? You’re correct that you need to set this programmatically. There’s no way in IB to indicate you should switch from top-bottom to side-to-side. Here's how you can do that:


  1. 选择其中一个轮换并确保所有约束都按照您想要的方式设置
    它在界面构建器中 - 例如,每个彩色的
    视图从superview中放置8个点(您的间隔视图)。底部的明确约束和
    更新框架按钮将帮助您,并且您需要经常点击
    以确保它同步。

  2. 非常重要的是,左上角的视图只能通过左侧(前端)和顶部连接到
    超级视图,而右下角的
    只能通过右侧(尾部)和底部连接。如果您清除
    设置高度和宽度固定的尺寸,这将产生
    警告。这是正常的,在这种情况下可以通过设置
    等宽度和等于来解决高度和必要时步骤3的一部分。
    (注意常量必须为零才能使值真正相等。)
    在其他情况下,我们必须设置约束并将其标记为占位符为
    使编译器静音,如果我们确定我们将填充信息,但编译器不知道。

  3. 识别(或创建)两个约束条件将右/底
    视图链接到左侧和左侧的内容您可能希望使用IB左侧的对象浏览器。使用助手编辑器在viewBtroller.h的
    中创建两个出口。看起来像:

  1. Pick one of the rotation and make sure all constraints are set up the way you want it in Interface builder- for example, each colored view puts 8 points (your spacer view) from superview. The "clear constraints" and "update frames" buttons in the bottom will help you and you’ll want to click it often to make sure it’s in sync.
  2. Very important that the top-left view only be connected to the superview by the left(leading) and top sides, and the bottom right only connected by the right(trailing) and bottom sides. If you clear the sizes setting the height and width fixed, this will produce a warning. This is normal, and in this case can be solved by setting "equal widths" and"equal heights" and part of step 3 if necessary. (Note the constant must be zero for the values to be truly equal.) In other cases we must put a constraint and mark it "placeholder" to silence the compiler, if we’re sure we'll be filling information but the compiler doesn’t know that.
  3. Identify (or create) the two constraints that links the right/bottom view to something to the left and to the top. You might want to use the object browser to the left of IB. Create two outlets in the viewController.h using assistant editor. Will look like:

@property(弱,非原子)IBOutlet NSLayoutConstraint * bottomViewToTopConstraint;
@property(弱,非原子)IBOutlet NSLayoutConstraint * rightViewToLeftConstraint;

在viewController中实现updateConstraints。这是
逻辑的去处:

Implement updateConstraints in the viewController. Here’s where the logic will go:

-(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相关。但这意味着当自动布局计算此视图时,您已根据其他可能已更改的信息做出假设。交换视图并更改它们相关的因素,这些因素旨在相互关联。

Since you can’t change constraints after they’re added (except the constant) we have to do this remove-add step. Notice the ones in IB might as well be placeholders, since we’re removing them every time (we could check first). We could modify the constant to some offset value, for example relating to the superview by spacer + topViewHight + spacer. But this mean that when auto layout goes to calculate this view, you’ve made assumptions based on some other information, which could have changed. Swapping out the views and changing what they relate the factors that are meant to change each other connected.

请注意,因为Auto Layout将在传递信息时使用约束,所以首先我们修改它们,然后我们调用super。这是调用超类私有实现来为此视图进行计算,而不是视图层次结构中此视图的超级视图,尽管实际上下一步将在树的更上一层。

Note that because Auto Layout will use the constraints here when passing information up, first we modify them, then we call super. This is calling the super class private implementation to do the calculations for this view, not the superview of this view in the view hierarchy, although in fact the next step will be further up the tree.

这篇关于超视图框架尺寸更改后更新约束的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆