超视图框架尺寸更改后更新约束

时间:2015-02-02 21:22:29

标签: ios objective-c uiview autolayout

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

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

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

#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

1 个答案:

答案 0 :(得分:12)

通常,将帧与自动布局混合是一个坏主意。 (例外是一个视图层次结构,它使用包含不包含视图的约束,然后不使用该点的任何约束[和其他警告])。一个大问题是约束系统通常不会从setFrame中获取任何信息。

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


修复

离开你的地方?你需要以编程方式设置它是正确的。在IB中没有办法表明你应该从上到下切换到左右。以下是您可以这样做的方法:

  1. 选择其中一个旋转并确保设置所有约束 您在“界面”构建器中所需的方式 - 例如,每个颜色 视图从superview中提取8个点(你的间隔视图)。 “明确约束”和 底部的“更新框架”按钮将帮助您,您将需要单击 通常是为了确保它同步。
  2. 非常重要的是,左上角的视图只能连接到 左侧(前方)和上方以及右下方的超视图 仅由右侧(尾部)和底部连接。如果你清楚 设置高度和宽度固定的尺寸,这将产生一个 警告。这是正常的,在这种情况下可以通过设置来解决 “相等宽度”和“相等高度”,如果需要,还可以是步骤3的一部分。 (注意,为了使值真正相等,常量必须为零。) 在其他情况下,我们必须设置一个约束并将其标记为“占位符” 沉默编译器,如果我们确定我们将填充信息,但编译器不知道。
  3. 识别(或创建)链接右/底部的两个约束 查看左侧和顶部的内容。您可能希望使用IB左侧的对象浏览器。在中创建两个出口 viewController.h使用助手编辑器。看起来像:

    @property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomViewToTopConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *rightViewToLeftConstraint;

  4. 在viewController中实现updateConstraints。这是哪里的 逻辑将去:

  5. -(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。这是调用超类私有实现来为此视图进行计算,而不是视图层次结构中此视图的超级视图,尽管事实上下一步将在树的更上一层。