天天看点

iOS 12 Auto Layout界面自动布局系列3-使用原生NSLayoutConstraint添加布局约束NSLayoutConstraint类的定义使用NSLayoutConstraint添加布局约束布局锚点总结

本系列的第一篇文章介绍了自动布局的基本原理,第二篇文章通过一个简单的例子演示了如何使用Xcode的Interface Builder(简称IB)以可视化方式添加约束。本篇为该系列的第三篇文章,主要介绍如何通过写代码来添加布局约束。

说句题外话,不论是通过IB可视化加约束,还是写代码加约束,这两种方式各有优劣。写代码加约束是最基础最灵活的方式,但缺点是当界面较复杂时代码量会很繁冗易错。而通过IB可视化方式设置约束,操作简单直观,并支持在设计器中实时预览布局效果,但缺点是并非所有约束都能用IB来添加,而且不容易后期维护。所以掌握写代码添加自动布局约束是非常必要的。原本这篇文章是本系列的第二篇,但为了提高读者理解和接受的程度,最终还是把本编放在第三的位置。OK闲言少叙,我们进入正题。

NSLayoutConstraint类的定义

第一篇文章讲到,每一个布局约束就是一个明确的线性变化规则,在数学上是以一次函数的形式表示,即:

y = m * x + c   (公式3.1)

例如,对于下图计算器App,若想表达按钮5的顶部与按钮8的底部对齐,根据公式3.1,建立以下线性关系即可:

按钮5.顶部 = 1.0 * 按钮8.底部 + 0.0

这就是一个布局约束。

iOS 12 Auto Layout界面自动布局系列3-使用原生NSLayoutConstraint添加布局约束NSLayoutConstraint类的定义使用NSLayoutConstraint添加布局约束布局锚点总结

在UIKit中,每一个布局约束是一个NSLayoutConstraint实例。NSLayoutConstraint类的定义如下:

NS_CLASS_AVAILABLE_IOS(6_0)
@interface NSLayoutConstraint : NSObject
...
@property (readonly, assign) id firstItem;
@property (readonly) NSLayoutAttribute firstAttribute;
@property (readonly) NSLayoutRelation relation;
@property (nullable, readonly, assign) id secondItem;
@property (readonly) NSLayoutAttribute secondAttribute;
@property (readonly) CGFloat multiplier;
@property CGFloat constant;
...
+(instancetype)constraintWithItem:(id)firstItem attribute:(NSLayoutAttribute)firstAttribute 
 relatedBy:(NSLayoutRelation)relation 
 toItem:(id)secondItem attribute:(NSLayoutAttribute)secondAttribute 
 multiplier:(CGFloat)multiplier constant:(CGFloat)constant;
           

其中firstItem与secondItem分别是界面中受约束的视图与被参照的视图。对于按钮5顶部与按钮8底部对齐的例子来说,则firstItem是按钮5,secondItem是按钮8。(需要注意的是,它们不一定非得是兄弟关系或者父子关系,只要它们有着共同的祖先视图即可,这可是autoresizingMask无法做到的呦。)

firstAttribute与secondAttribute分别是firstItem与secondItem的某个布局属性(NSLayoutAttribute),它是一个枚举:

typedef NS_ENUM(NSInteger, NSLayoutAttribute)
{
    NSLayoutAttributeLeft = 1,
    NSLayoutAttributeRight,
    NSLayoutAttributeTop,
    NSLayoutAttributeBottom,
    NSLayoutAttributeLeading,
    NSLayoutAttributeTrailing,
    NSLayoutAttributeWidth,
    NSLayoutAttributeHeight,
    NSLayoutAttributeCenterX,
    NSLayoutAttributeCenterY,
    NSLayoutAttributeBaseline,
    NSLayoutAttributeNotAnAttribute = 0,
    ......//省略剩余
};
           

每一个枚举值代表了一个布局属性,名字都很直观,例如Left代表左侧,Height代表高度等等。对于按钮5顶部与按钮8底部对齐的例子来说,firstAttribute是NSLayoutAttributeTop,secondItem是NSLayoutAttributeBottom。(注意,firstItem与secondItem不一定非得是同样的值,允许定义诸如某视图的高度等于另一个视图的宽度这样的约束,尽管很少这样做。)

这里额外解释一下NSLayoutAttributeNotAnAttribute,当需要为视图的宽度或高度指定固定值时(例如希望某个视图宽度固定为100),这时候secondItem为nil,secondAttribute为NSLayoutAttributeNotAnAttribute。

relation定义了布局关系(NSLayoutRelation):

typedef NS_ENUM(NSInteger, NSLayoutRelation)
{
    NSLayoutRelationLessThanOrEqual = -1,
    NSLayoutRelationEqual = 0,
    NSLayoutRelationGreaterThanOrEqual = 1,
};
           

布局关系不仅限于相等,还可以是大于等于或者小于等于,这种不等关系在处理UILabel、UIImageView等具有自身内容尺寸的控件(自身内容尺寸参见本系列第五篇文章)时非常常用。举个简单的例子,UILabel的长度会随文字的长度而变化,那么我们可以向UILabel控件添加两个约束,分别是“长度大于等于50”与“长度小于等于200”。这样,当文字很少时,宽度也至少为50;当文字非常多时,宽度也不会超过200。

multiplier即比例系数。constant即常量。

因此,每个约束就对应如下关系:

firstItem.firstAttribute {==,<=,>=} secondItem.secondAttribute * multiplier + constant   (公式3.2)

我们可以调用NSLayoutConstraint类的constraintWithItem:…方法,传入所有需要的参数构造一个新的约束。

例如,要想表达按钮5的顶部与按钮8的底部对齐,根据公式3.2,代码如下:

[NSLayoutConstraint constraintWithItem:button5 
    attribute:NSLayoutAttributeTop 
    relatedBy:NSLayoutRelationEqual 
    toItem:button8 
    attribute:NSLayoutAttributeBottom 
    multiplier:1.0f 
    constant:0.0f];
           

是不是很简单?

使用NSLayoutConstraint添加布局约束

理论就到此为止,下面我们还是以第二篇文章中的例子来讲解如何使用代码添加约束。Demo运行后的界面如下:

iOS 12 Auto Layout界面自动布局系列3-使用原生NSLayoutConstraint添加布局约束NSLayoutConstraint类的定义使用NSLayoutConstraint添加布局约束布局锚点总结
iOS 12 Auto Layout界面自动布局系列3-使用原生NSLayoutConstraint添加布局约束NSLayoutConstraint类的定义使用NSLayoutConstraint添加布局约束布局锚点总结

打开Xcode(10.1版),新建Single View App项目,项目命名为AutoLayoutByConstraint,本文使用Objective-C讲解,设备选择Universal。下载苹果Logo图片apple.jpg,并将其拖入项目中。文件下载地址:

链接:https://pan.baidu.com/s/1b5AqDo 密码:e4ff

首先,界面上方用来显示苹果Logo图片的是一个UIImageView,ViewController类的viewDidLoad方法如下:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    UIImageView* logoImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"apple.jpg"]];
    logoImageView.contentMode = UIViewContentModeScaleAspectFit;
    [self.view addSubview:logoImageView];
}
           

我们需要为logoImageView添加4个约束:

  • logoImageView左侧与父视图头部对齐
  • logoImageView右侧与父视图尾部对齐
  • logoImageView顶部与父视图顶部对齐
  • logoImageView高度为父视图高度一半

    根据公式3.2,在ViewController类的viewDidLoad方法末尾处构造上述4个约束,代码如下:

//logoImageView左侧与父视图头部对齐
    NSLayoutConstraint* leftConstraint = [NSLayoutConstraint 
        constraintWithItem:logoImageView 
        attribute:NSLayoutAttributeLeading 
        relatedBy:NSLayoutRelationEqual 
        toItem:logoImageView.superview 
        attribute:NSLayoutAttributeLeading 
        multiplier:1.0f constant:0.0f];
    
    //logoImageView右侧与父视图尾部对齐
    NSLayoutConstraint* rightConstraint = [NSLayoutConstraint 
        constraintWithItem:logoImageView 
        attribute:NSLayoutAttributeTrailing 
        relatedBy:NSLayoutRelationEqual 
        toItem:logoImageView.superview 
        attribute:NSLayoutAttributeTrailing 
        multiplier:1.0f constant:0.0f];
    
    //logoImageView顶部与父视图顶部对齐
    NSLayoutConstraint* topConstraint = [NSLayoutConstraint 
        constraintWithItem:logoImageView 
        attribute:NSLayoutAttributeTop 
        relatedBy:NSLayoutRelationEqual 
        toItem:logoImageView.superview 
        attribute:NSLayoutAttributeTop 
        multiplier:1.0f constant:0.0f];
    
    //logoImageView高度为父视图高度一半
    NSLayoutConstraint* heightConstraint = [NSLayoutConstraint 
        constraintWithItem:logoImageView 
        attribute:NSLayoutAttributeHeight 
        relatedBy:NSLayoutRelationEqual 
        toItem:logoImageView.superview 
        attribute:NSLayoutAttributeHeight 
        multiplier:0.5f constant:0.0f];
    
    //iOS 6.0或者7.0调用addConstraints
    //[self.view addConstraints:@[leftConstraint, rightConstraint, topConstraint, heightConstraint]];
    
    //iOS 8.0以后设置每个约束的active属性值为YES
    //leftConstraint.active = YES;
    //rightConstraint.active = YES;
    //topConstraint.active = YES;
    //heightConstraint.active = YES;
    
    ///iOS 8.0以后推荐使用activateConstraints:方法,效率比单独设置active属性要高
    [NSLayoutConstraint activateConstraints:@[leftConstraint, rightConstraint, topConstraint, heightConstraint]];
           

注意,约束在创建后默认是未激活的(不生效的),需要手动激活才能令约束生效。对于iOS 8及更新的版本,直接设置约束的active属性(BOOL值)或者调用activateConstraints:与deactivateConstraints:类方法。(对于iOS 6或者iOS 7可以调用addConstraint(s):和removeConstraint(s):方法。)

就是这么简单!现在编译并运行项目(模拟器iPhone 5s为例)。

iOS 12 Auto Layout界面自动布局系列3-使用原生NSLayoutConstraint添加布局约束NSLayoutConstraint类的定义使用NSLayoutConstraint添加布局约束布局锚点总结
iOS 12 Auto Layout界面自动布局系列3-使用原生NSLayoutConstraint添加布局约束NSLayoutConstraint类的定义使用NSLayoutConstraint添加布局约束布局锚点总结

貌似logoImageView的尺寸不太对,而且竖屏时图像未能居中。如果在viewDidLoad方法中将self.view的背景色设置为红色,看得会更清楚:

iOS 12 Auto Layout界面自动布局系列3-使用原生NSLayoutConstraint添加布局约束NSLayoutConstraint类的定义使用NSLayoutConstraint添加布局约束布局锚点总结

同时注意到Xcode控制台打印出了一大段信息:

2019-01-30 14:36:07.945820+0800 AutoLayoutByConstraint[11003:993889] [LayoutConstraints] Unable to simultaneously satisfy constraints.
	Probably at least one of the constraints in the following list is one you don't want. 
	Try this: 
		(1) look at each constraint and try to figure out which you don't expect; 
		(2) find the code that added the unwanted constraint or constraints and fix it. 
	(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSAutoresizingMaskLayoutConstraint:0x6000003cadf0 h=--& v=--& apple.jpg.midX == 120.5   (active, names: apple.jpg:0x7f9625f04530 )>",
    "<NSLayoutConstraint:0x6000003cba20 H:|-(0)-[apple.jpg]   (active, names: apple.jpg:0x7f9625f04530, '|':UIView:0x7f9625d27350 )>",
    "<NSLayoutConstraint:0x6000003cb890 apple.jpg.trailing == UIView:0x7f9625d27350.trailing   (active, names: apple.jpg:0x7f9625f04530 )>",
    "<NSLayoutConstraint:0x6000003c8280 'UIView-Encapsulated-Layout-Width' UIView:0x7f9625d27350.width == 568   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x6000003cb890 apple.jpg.trailing == UIView:0x7f9625d27350.trailing   (active, names: apple.jpg:0x7f9625f04530 )>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.

//我简要翻译一下上述内容:
//不能同时满足约束。或许下列约束中的至少其中一个并非如你所愿。尝试如下方法:
//(1) 检查每个约束,试着找出并不期望的约束。
//(2) 找到添加该约束的代码,并进行修正。
//(备注:如果你看到NSAutoresizingMaskLayoutConstraint却并不理解,请查阅UIview文档中的translatesAutoresizingMaskIntoConstraints属性。)
           

看来是出错了,为什么会这样?这是由于自动布局技术是苹果在iOS 6当中新加入的,但在那时仍然有很多项目代码使用autoresizingMask与setFrame:的方式构建界面。倘若将一个已经设置好frame并使用autoresizingMask的视图v1添加到一个使用自动布局的视图v2中时,运行时会使用自动布局来构建界面。为了保持对旧代码的兼容性,运行时会隐式地将v1的frame和autoresizingMask转化为自动布局约束(这些隐式转换的约束的类型为NSAutoresizingMaskLayoutConstraint),这样才能明确v1的位置与尺寸而不会导致约束缺失。这个隐式转换的过程,是由UIView的translatesAutoresizingMaskIntoConstraints属性的值决定的。默认情况下,为保证兼容性,该值为YES,表示需要自动进行隐式转换。这对于兼容旧的代码当然是好的(否则就需要对原有代码做大量修改),然而当我们明确为视图添加了约束后,我们就不希望再进行autoresizingMask的隐式转换了,否则就会引起约束的冲突(Confliction)。因此,需要特别注意的是,当我们使用代码创建视图时,需要将translatesAutoresizingMaskIntoConstraints属性的值设置为NO。在viewDidLoad方法中创建logoImageView的代码之后,添加如下代码:

logoImageView.translatesAutoresizingMaskIntoConstraints = NO;
           

再次运行,这次就没问题了。

iOS 12 Auto Layout界面自动布局系列3-使用原生NSLayoutConstraint添加布局约束NSLayoutConstraint类的定义使用NSLayoutConstraint添加布局约束布局锚点总结

到这里,我想你应该可以把剩余的视图和约束的代码添加上了,全部代码如下:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // self.view.backgroundColor = [UIColor redColor];
    
    UIImageView* logoImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"apple.jpg"]];
    logoImageView.translatesAutoresizingMaskIntoConstraints = NO;
    logoImageView.contentMode = UIViewContentModeScaleAspectFit;
    [self.view addSubview:logoImageView];
    
    //logoImageView左侧与父视图左侧对齐
    NSLayoutConstraint* leftConstraint = [NSLayoutConstraint constraintWithItem:logoImageView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1.0f constant:0.0f];
    
    //logoImageView右侧与父视图右侧对齐
    NSLayoutConstraint* rightConstraint = [NSLayoutConstraint constraintWithItem:logoImageView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:0.0f];
    
    //logoImageView顶部与父视图顶部对齐
    NSLayoutConstraint* topConstraint = [NSLayoutConstraint constraintWithItem:logoImageView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f];
    
    //logoImageView高度为父视图高度一半
    NSLayoutConstraint* heightConstraint = [NSLayoutConstraint constraintWithItem:logoImageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:0.5f constant:0.0f];
    
    //iOS 6.0或者7.0调用addConstraints
    //    [self.view addConstraints:@[leftConstraint, rightConstraint, topConstraint, heightConstraint]];
    
    //iOS 8.0以后设置active属性值
    //leftConstraint.active = YES;
    //rightConstraint.active = YES;
    //topConstraint.active = YES;
    //heightConstraint.active = YES;
    
    //iOS 8.0以后推荐使用activateConstraints:方法,效率比单独设置active属性要高
    [NSLayoutConstraint activateConstraints:@[leftConstraint, rightConstraint, topConstraint, heightConstraint]];
    
    UIScrollView* scrollView = [UIScrollView new];
    scrollView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:scrollView];
    
    //scrollView左侧与父视图左侧对齐
    NSLayoutConstraint* scrollLeftConstraint = [NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1.0f constant:0.0f];
    
    //scrollView右侧与父视图右侧对齐
    NSLayoutConstraint* scrollRightConstraint = [NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:0.0f];
    
    //scrollView底部与父视图底部对齐
    NSLayoutConstraint* scrollBottomConstraint = [NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0.0f];
    
    //scrollView顶部与logoImageView底部对齐
    NSLayoutConstraint* scrollTopConstraint = [NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:logoImageView attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0.0f];
    
    [NSLayoutConstraint activateConstraints:@[scrollLeftConstraint, scrollRightConstraint, scrollBottomConstraint, scrollTopConstraint]];
    
    UILabel* nameLabel = [UILabel new];
    nameLabel.translatesAutoresizingMaskIntoConstraints = NO;
    nameLabel.text = @"苹果公司";
    nameLabel.backgroundColor = [UIColor greenColor];
    [scrollView addSubview:nameLabel];
    
    UILabel* descriptionLabel = [UILabel new];
    descriptionLabel.translatesAutoresizingMaskIntoConstraints = NO;
    descriptionLabel.text = @"苹果公司(Apple Inc. )是美国的一家高科技公司。由史蒂夫·乔布斯、斯蒂夫·沃兹尼亚克和罗·韦恩(Ron Wayne)等三人于1976年4月1日创立,并命名为美国苹果电脑公司(Apple Computer Inc. ), 2007年1月9日更名为苹果公司,总部位于加利福尼亚州的库比蒂诺。\n苹果公司创立之初主要开发和销售的个人电脑,截至2014年致力于设计、开发和销售消费电子、计算机软件、在线服务和个人计算机。苹果的Apple II于1970年代助长了个人电脑革命,其后的Macintosh接力于1980年代持续发展。该公司硬件产品主要是Mac电脑系列、iPod媒体播放器、iPhone智能手机和iPad平板电脑;在线服务包括iCloud、iTunes Store和App Store;消费软件包括OS X和iOS操作系统、iTunes多媒体浏览器、Safari网络浏览器,还有iLife和iWork创意和生产力套件。苹果公司在高科技企业中以创新而闻名世界。\n苹果公司1980年12月12日公开招股上市,2012年创下6235亿美元的市值记录,截至2014年6月,苹果公司已经连续三年成为全球市值最大公司。苹果公司在2014年世界500强排行榜中排名第15名。2013年9月30日,在宏盟集团的“全球最佳品牌”报告中,苹果公司超过可口可乐成为世界最有价值品牌。2014年,苹果品牌超越谷歌(Google),成为世界最具价值品牌 。";
    descriptionLabel.numberOfLines = 0;
    descriptionLabel.backgroundColor = [UIColor yellowColor];
    [scrollView addSubview:descriptionLabel];
    
    //nameLabel左侧与父视图左侧对齐
    NSLayoutConstraint* nameLabelLeftConstraint = [NSLayoutConstraint constraintWithItem:nameLabel attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeLeading multiplier:1.0f constant:0.0f];
    
    //nameLabel右侧与父视图右侧对齐
    NSLayoutConstraint* nameLabelRightConstraint = [NSLayoutConstraint constraintWithItem:nameLabel attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:0.0f];
    
    //nameLabel底部与descriptionLabel顶部对齐
    NSLayoutConstraint* nameLabelBottomConstraint = [NSLayoutConstraint constraintWithItem:nameLabel attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:descriptionLabel attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f];
    
    //nameLabel顶部与父视图顶部对齐
    NSLayoutConstraint* nameLabelTopConstraint = [NSLayoutConstraint constraintWithItem:nameLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f];
    
    //nameLabel高度为20
    NSLayoutConstraint* nameLabelHeightConstraint = [NSLayoutConstraint constraintWithItem:nameLabel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:20.0f];
    
    [NSLayoutConstraint activateConstraints:@[nameLabelLeftConstraint, nameLabelRightConstraint, nameLabelBottomConstraint, nameLabelTopConstraint, nameLabelHeightConstraint]];
    
    //descriptionLabel左侧与父视图左侧对齐
    NSLayoutConstraint* descriptionLabelLeftConstraint = [NSLayoutConstraint constraintWithItem:descriptionLabel attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeLeading multiplier:1.0f constant:0.0f];
    
    //descriptionLabel右侧与父视图右侧对齐
    NSLayoutConstraint* descriptionLabelRightConstraint = [NSLayoutConstraint constraintWithItem:descriptionLabel attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:0.0f];
    
    //descriptionLabel底部与父视图底部对齐
    NSLayoutConstraint* descriptionLabelBottomConstraint = [NSLayoutConstraint constraintWithItem:descriptionLabel attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0.0f];
    
    [NSLayoutConstraint activateConstraints:@[descriptionLabelLeftConstraint, descriptionLabelRightConstraint, descriptionLabelBottomConstraint]];
    
    //nameLabel宽度与logoImageView宽度相等
    NSLayoutConstraint* nameLabelWidthConstraint = [NSLayoutConstraint constraintWithItem:nameLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:logoImageView attribute:NSLayoutAttributeWidth multiplier:1.0f constant:0.0f];
    
    //nameLabel宽度与logoImageView宽度相等
    NSLayoutConstraint* descriptionLabelWidthConstraint = [NSLayoutConstraint constraintWithItem:descriptionLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:logoImageView attribute:NSLayoutAttributeWidth multiplier:1.0f constant:0.0f];
    
    [NSLayoutConstraint activateConstraints:@[nameLabelWidthConstraint, descriptionLabelWidthConstraint]];
}
           

程序最终项目文件链接:https://github.com/puckerxp/AutoLayoutSeries

(Demo位于AutoLayoutByConstraint目录下)。

自动布局约束是通过描述视图间的关系而非强加坐标值来进行定位的,它更能满足不同设备尺寸的界面布局,并且更容易让人理解。虽然上面的代码很冗长,但每一句所描述的事实都十分清楚。在此省略自动布局的好处10000字。。。

布局锚点

可能你还是觉得,区区几个简单的视图,就要写这么长的代码。。。

iOS 12 Auto Layout界面自动布局系列3-使用原生NSLayoutConstraint添加布局约束NSLayoutConstraint类的定义使用NSLayoutConstraint添加布局约束布局锚点总结

每次生成NSLayoutConstraint实例时都需要传入7个参数,每个视图又至少需要创建4个NSLayoutConstraint实例,这确实是一件比较辛苦的事情。其实大多数情况下,我们只是关心对齐或者居中,几乎不怎么修改multiplier(默认为1.0f)和constant的值(默认为0.0f)。有没有简化方法呢?那你不妨试试布局锚点表示法,这种方式牺牲了部分灵活性,但使得代码简化易读。

iOS 9为每个UIView对象添加了若干锚点属性,表示该视图的上下左右边缘,以及水平中心、垂直中心、基准线等特殊位置,用来建立约束。

@interface UIView (UIViewLayoutConstraintCreation)
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *leadingAnchor NS_AVAILABLE_IOS(9_0);
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *trailingAnchor NS_AVAILABLE_IOS(9_0);
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *leftAnchor NS_AVAILABLE_IOS(9_0);
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *rightAnchor NS_AVAILABLE_IOS(9_0);
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *topAnchor NS_AVAILABLE_IOS(9_0);
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *bottomAnchor NS_AVAILABLE_IOS(9_0);
@property(nonatomic,readonly,strong) NSLayoutDimension *widthAnchor NS_AVAILABLE_IOS(9_0);
@property(nonatomic,readonly,strong) NSLayoutDimension *heightAnchor NS_AVAILABLE_IOS(9_0);
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *centerXAnchor NS_AVAILABLE_IOS(9_0);
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *centerYAnchor NS_AVAILABLE_IOS(9_0);
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *firstBaselineAnchor NS_AVAILABLE_IOS(9_0);
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *lastBaselineAnchor NS_AVAILABLE_IOS(9_0);
@end
           

通过这些布局锚点就可以很方便地创建布局,直接看代码吧:

// headerImageView固定高度100pt
[headerImageView.heightAnchor 
    constraintEqualToConstant:100.0f].active = YES;

// logoImageView高度为父视图高度一半
[logoImageView.heightAnchor 
    constraintEqualToAnchor:logoImageView.superView.heightAnchor 
    multiplier:0.5f].active = YES;

// 按钮5顶部与按钮8底部对齐
[button5.topAnchor 
    constraintEqualToAnchor:button8.bottomAnchor].active = YES;
           

是不是简单多了?

这些锚点属性是只读的,能够获取到NSLayoutAnchor子类的实例,类图关系如下:

iOS 12 Auto Layout界面自动布局系列3-使用原生NSLayoutConstraint添加布局约束NSLayoutConstraint类的定义使用NSLayoutConstraint添加布局约束布局锚点总结

这些类提供了创建NSLayoutConstraint实例的各种方法:

iOS 12 Auto Layout界面自动布局系列3-使用原生NSLayoutConstraint添加布局约束NSLayoutConstraint类的定义使用NSLayoutConstraint添加布局约束布局锚点总结

总结

通过编写代码来创建约束是必须要掌握的技巧,不过确实是稍微麻烦了一些,基本上都需要修改并检查好几次,又调试了好几次才完全写对。为了能够提高成功率,本人强烈建议将每个视图的4个必要约束放在一起写。这样一旦出现问题,你也好调试和定位。

另外,很多人觉得苹果提供的添加约束方法不够简洁优雅,毕竟之前一句setFrame搞定的事,现在要分成4句来写,每句还那么多参数。我认为是他们没有找到好的方法,如果他们能够善用Code Snippet,那么我想他们没有任何理由再喜欢setFrame:CGRectMake…或者找第三方库如Masonry之类的了(我之前的项目从未用过Masonry之类的第三方布局库,因为当你熟悉之后,原生完全够用)。(关于Code Snippet我会再写一篇文章来详细介绍)

在下一篇文章中,我将介绍另一种更简洁的方式,即使用VFL来添加约束,敬请期待吧。