天天看點

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來添加限制,敬請期待吧。