滚动视图和 UI 组件的通用自动布局

Posted

技术标签:

【中文标题】滚动视图和 UI 组件的通用自动布局【英文标题】:Universal Autolayout for Scrollview & UI components 【发布时间】:2015-09-11 16:51:13 【问题描述】:

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

您能告诉我如何为 Universal App 设置自动布局吗?我尝试限制边距并将前导、顶部、尾随空间固定到容器,但它不起作用。

【问题讨论】:

【参考方案1】:

实际上,自动布局在滚动视图中的工作方式与在其他视图中不同。在滚动视图中,前导、尾随、顶部和底部约束形成容器视图的超级视图(在本例中为滚动视图)定义的不是间距,而是“我的滚动视图应该向左、向右、顶部和该组件的底部”。

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

    设置从 Scrollview 到视图的前导、尾随、顶部和底部约束

    向滚动视图添加一个子视图,您将使用它作为引导视图。由于滚动视图的宽度因设备而异,因此您需要一个与滚动视图的宽度相关的视图,因此您可以设置与该视图相关的约束,而不是滚动视图的左右边框(因为这不会定义大小,但是“您的滚动视图向左或向右滚动多少”)。此视图的高度 = 0,位于 0,0,并且与滚动视图具有相同的宽度。

    为该视图设置约束。高度 = 0。容器的前导空间和容器的尾随空间设置为 0(这告诉您的滚动视图不要滚动到引导视图的两侧)。也为滚动视图添加一个顶部约束,因为您的引导视图将位于滚动视图的顶部。还要在滚动视图和引导视图之间添加等宽关系(这非常重要,它将强制引导视图与滚动视图具有相同的宽度,因此没有水平滚动可用)。请注意,此时,您的滚动视图知道它必须向左、向右和顶部滚动多少,而不是向下滚动,因此您会在自动布局中看到一个错误。

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

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

希望对你有帮助,

【讨论】:

【参考方案2】:

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

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

肖像:

风景:

//
//  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

【讨论】:

以上是关于滚动视图和 UI 组件的通用自动布局的主要内容,如果未能解决你的问题,请参考以下文章

使用自动布局读取 UI 正确框架

UITextView 使用自动布局动态扩展为滚动视图内的文本

尽管添加了自动布局约束,但 UIScrollView 不滚动

Swift - UIScrollView 内的 UICollectionView - 自动布局

使用 XIB 和自动布局操作视图层次结构

滚动视图不在自动布局中滚动子视图