Scrollview 和 UI 组件的通用自动布局

Universal Autolayout for Scrollview & UI components

我知道自动布局,但我不熟悉具有自动布局的通用应用程序。我在为下图设置 Constrain 时遇到问题。

你能告诉我如何为 Universal App 设置自动布局吗?我尝试使用 constrain to margin & pin the leading, top, trailing space to container 但它不起作用。

我不知道自动布局,但下面的代码也能正常工作。我更喜欢代码中的约束,它更容易管理、修改和理解。我已经在所有设备尺寸上测试了下面的内容..

注意:您不必将所有内容都放在一个文件中。创建不同的视图并让每个视图处理自己的 constraints/layouts。

纵向:

横向:

//
//  ViewController.m
//  test
//
//  Created by Brandon T on 2015-09-11.
//  Copyright (c) 2015 Brandon T. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UIView *paleometerView;
@property (nonatomic, strong) UIView *maleometerView;
@property (nonatomic, strong) UIView *influenceView;
@property (nonatomic, strong) UIButton *letCompanyKnowButton;
@property (nonatomic, strong) UIButton *shareWithFriendsButton;
@end

@implementation ViewController

- (instancetype)init {
    if (self = [super init]) {
        [self initControls];
    }
    return self;
}

/*- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        [self initControls];
    }
    return self;
}*/

/*- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
        [self initControls];
    }
    return self;
}*/

- (void)initControls {
    self.paleometerView = [[UIView alloc] init];
    self.maleometerView = [[UIView alloc] init];
    self.influenceView = [[UIView alloc] init];
    self.letCompanyKnowButton = [[UIButton alloc] init];
    self.shareWithFriendsButton = [[UIButton alloc] init];

    [self.paleometerView setBackgroundColor:[UIColor lightGrayColor]];
    [self.maleometerView setBackgroundColor:[UIColor grayColor]];

    [self.influenceView setBackgroundColor:[UIColor yellowColor]];
    [self.letCompanyKnowButton setBackgroundColor:[UIColor redColor]];
    [self.shareWithFriendsButton setBackgroundColor:[UIColor greenColor]];

    [self.view setBackgroundColor:[UIColor grayColor]];

    [self.letCompanyKnowButton setTitle:@"Let Company Know" forState:UIControlStateNormal];
    [self.shareWithFriendsButton setTitle:@"Share With Friends" forState:UIControlStateNormal];
}

- (void)viewDidLoad {
    [super viewDidLoad];


    [self initControls];

    [self layoutPaleMaleView];
    [self layoutInfluenceView];
    [self layoutAllViews];
}

- (void)layoutPaleMaleView {
    UILabel *paleLabel = [[UILabel alloc] init];
    UILabel *maleLabel = [[UILabel alloc] init];

    [paleLabel setText:@"Paleomenter\n85%\nPale"];
    [maleLabel setText:@"Maleometer\n71%\nMale"];

    [paleLabel setTextAlignment:NSTextAlignmentCenter];
    [maleLabel setTextAlignment:NSTextAlignmentCenter];

    [paleLabel setNumberOfLines:0];
    [maleLabel setNumberOfLines:0];

    [paleLabel setLineBreakMode:NSLineBreakByWordWrapping];
    [maleLabel setLineBreakMode:NSLineBreakByWordWrapping];

    [self.paleometerView addSubview:paleLabel];
    [self.maleometerView addSubview:maleLabel];

    [self.paleometerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[pale]-0-|" options:0 metrics:nil views:@{@"pale":paleLabel}]];
    [self.paleometerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[pale]-0-|" options:0 metrics:nil views:@{@"pale":paleLabel}]];

    [self.maleometerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[male]-0-|" options:0 metrics:nil views:@{@"male":maleLabel}]];
    [self.maleometerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[male]-0-|" options:0 metrics:nil views:@{@"male":maleLabel}]];

    [paleLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
    [maleLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
}

- (void)layoutInfluenceView {
    UIView *left = [[UIView alloc] init];
    UIView *middle = [[UIView alloc] init];
    UIView *right = [[UIView alloc] init];

    [left setBackgroundColor:[UIColor blueColor]];
    [middle setBackgroundColor:[UIColor yellowColor]];
    [right setBackgroundColor:[UIColor purpleColor]];

    [self.influenceView addSubview:left];
    [self.influenceView addSubview:middle];
    [self.influenceView addSubview:right];

    //Left, right and middle all have the same width.
    [self.influenceView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[left(==middle)]-0-[middle(==right)]-0-[right(==left)]-0-|" options:0 metrics:nil views:@{@"left":left, @"middle":middle, @"right":right}]];

    [self.influenceView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[left]-0-|" options:0 metrics:nil views:@{@"left":left}]];

    [self.influenceView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[middle]-0-|" options:0 metrics:nil views:@{@"middle":middle}]];

    [self.influenceView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[right]-0-|" options:0 metrics:nil views:@{@"right":right}]];

    [left setTranslatesAutoresizingMaskIntoConstraints:NO];
    [middle setTranslatesAutoresizingMaskIntoConstraints:NO];
    [right setTranslatesAutoresizingMaskIntoConstraints:NO];
}

- (void)layoutAllViews {
    [self.view addSubview:self.paleometerView];
    [self.view addSubview:self.maleometerView];
    [self.view addSubview:self.influenceView];
    [self.view addSubview:self.letCompanyKnowButton];
    [self.view addSubview:self.shareWithFriendsButton];


    NSDictionary *views = @{@"paleometer":self.paleometerView, @"maleometer":self.maleometerView, @"influence":self.influenceView, @"letCompanyKnow":self.letCompanyKnowButton, @"share":self.shareWithFriendsButton};

    NSMutableArray *constraints = [[NSMutableArray alloc] init];

    //Constrain *Horizontally* Paleometer and Maleometer to be equal widths, 0 spacing from the left and 0 spacing from the right, with 0 spacing between them..
    [constraints addObject:@"H:|-0-[paleometer(==maleometer)]-0-[maleometer(==paleometer)]-0-|"];

    //Constrain *Horizontally* InfluenceView to be 0 spacing from the left and 0 spacing from the right.
    [constraints addObject:@"H:|-0-[influence]-0-|"];

    //Constrain *Horizontally* the "letCompanyKnowButton" with 20 spacing on the left and 20 spacing on the right..
    [constraints addObject:[NSString stringWithFormat:@"H:|-%d-[letCompanyKnow]-%d-|", 20, 20]];

    //Constrain *Horizontally* the "shareButton" with 20 spacing on the left and 20 spacing on the right..
    [constraints addObject:[NSString stringWithFormat:@"H:|-%d-[share]-%d-|", 20, 20]];

    //Constrain *Vertically* the paleometer with 0 spacing from the top and 150 in height.
    //Below it we have 0 spacing and then the influenceView which has a minimum height of 250 but is flexible so it will size to fit whenever needed.
    //Next we have another 20 spacing below the influenceView and the letCompanyKnow button which has a height of 50 and has a 20 spacing above and below it.
    //Finally the shareWithFriends button has the same height as the letCompanyKnow button.
    [constraints addObject:@"V:|-0-[paleometer(150)]-0-[influence(250@500)]-20-[letCompanyKnow(50)]-20-[share(==letCompanyKnow)]-20-|"];

    //Constrain *Vertically* the maleometer to be equal height with paleometer.
    [constraints addObject:@"V:[maleometer(==paleometer)]"];

    //Make influenceView flexible height for both shrinking and expanding vertically.
    [self.influenceView setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical];
    [self.influenceView setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical];

    //add the constraints to the view.
    for (NSString *constraint in constraints) {
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:constraint options:0 metrics:nil views:views]];
    }

    for (UIView *view in self.view.subviews) {
        [view setTranslatesAutoresizingMaskIntoConstraints:NO];
    }
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

实际上,自动布局在滚动视图中的工作方式与在其他视图中不同。在滚动视图中,前导、尾随、顶部和底部约束形成容器视图(在本例中为滚动视图)的超级视图,定义的不是间距,而是 "how much my scroll view should scroll to the left, right, top and bottom of this component".

的类型

所以,要在滚动视图中使用自动布局,您必须做一些棘手的事情:

  1. 设置从滚动视图到视图的前导、尾随、顶部和底部约束

  1. 向滚动视图添加一个子视图,您将用作引导视图。由于滚动视图的宽度因设备而异,因此您需要一个与滚动视图的宽度相关的视图,因此您可以设置与该视图相关的约束而不是滚动视图的左右边框(因为这不会定义大小, 但 "how much your scrollview scrolls to left or right").此视图的高度 = 0,位于 0,0,并且与滚动视图具有相同的宽度。

  1. 为该视图设置约束。 Height = 0. Leading space to container,Trailing space to container 设置为 0(这告诉你的滚动视图不要滚动到引导视图的两侧)。也向滚动视图添加顶部约束,因为您的引导视图将位于滚动视图的顶部。还要在你的滚动视图和你的引导视图之间添加一个等宽关系(这很重要,它会强制你的引导视图与滚动视图具有相同的宽度,因此水平滚动将不可用)。请注意,此时,您的滚动视图知道它必须向左、向右和顶部滚动多少,但不知道滚动到底部,因此您会在自动布局中看到一个错误。

  1. 现在你构建你的 UI,考虑到顶部、左侧和右侧的关系必须与引导视图相关,而不是与滚动视图相关(你想定义间距,而不是 "amount of scroll").很难把所有需要的约束放在这里,所以我在 github 上创建了一个示例项目:Download it here

当您打开项目时,请注意对象和滚动视图之间的所有约束并不反映间距(如我所说),而是反映您的滚动视图应滚动远离组件的程度。

希望这对您有所帮助,