Auto Layout 翻譯過(guò)來(lái)就是自動(dòng)布局。在ios中Auto Layout 會(huì)根據(jù)我們?cè)谝晥D上所設(shè)置的約束來(lái)動(dòng)態(tài)地計(jì)算視圖層次中所有視圖的大小和位置矾端。即使我們使用不同尺寸的手機(jī)屏幕掏击,或者橫屏豎屏展示我們的頁(yè)面,自動(dòng)布局總是能夠適應(yīng)這些變化秩铆,讓頁(yè)面上的元素按照我們想要的樣子展示出來(lái)砚亭。Auto Layout 內(nèi)容很多,包括:UIStackView殴玛、UILayoutGuide捅膘、NSLayoutConstraint、NSLayoutAnchor滚粟、SizeClasses寻仗、Constraints in Interface Builder等,本文主要介紹的是 NSLayoutConstraint凡壤。
使用代碼為控件添加約束主要有分為以下兩個(gè)步驟:
步驟1: 實(shí)例化一個(gè) NSLayoutConstraint 約束對(duì)象署尤;
步驟2: 使步驟1中生成的約束生效
使用代碼創(chuàng)建 NSLayoutConstraint 最核心的步驟就是上面兩步。下面會(huì)詳細(xì)進(jìn)行說(shuō)明亚侠。
1.實(shí)例化一個(gè) NSLayoutConstraint 約束對(duì)象
創(chuàng)建 NSLayoutConstraint 對(duì)象最常用的方法是:
+(instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(nullable id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;
NSLayoutConstraint 是界面上兩個(gè)視圖對(duì)象之間的關(guān)系曹体,必須滿足于基于約束的布局系統(tǒng)。這個(gè)系統(tǒng)剛好構(gòu)成一個(gè)線性方程硝烂,格式如下:
view1.attribute1 = multiplier × view2.attribute2 + constant
這個(gè)方法中包含七個(gè)參數(shù)箕别,下面分別解釋一下:
- view1: 要約束的視圖
- attr1: 約束的類型,是一個(gè) NSLayoutAttribute 常量钢坦,有如下幾個(gè)值究孕,根據(jù)名字也可以看出這些值代表的意義,這里不再贅述爹凹。
typedef NS_ENUM(NSInteger, NSLayoutAttribute) {
NSLayoutAttributeLeft = 1,
NSLayoutAttributeRight,
NSLayoutAttributeTop,
NSLayoutAttributeBottom,
NSLayoutAttributeLeading,
NSLayoutAttributeTrailing,
NSLayoutAttributeWidth,
NSLayoutAttributeHeight,
NSLayoutAttributeCenterX,
NSLayoutAttributeCenterY,
NSLayoutAttributeLastBaseline,
NSLayoutAttributeBaseline NS_SWIFT_UNAVAILABLE("Use 'lastBaseline' instead") = NSLayoutAttributeLastBaseline,
NSLayoutAttributeFirstBaseline NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeLeftMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeRightMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeTopMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeBottomMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeLeadingMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeTrailingMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeCenterXWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeCenterYWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeNotAnAttribute = 0
};
- relation: 與參照視圖 view2 之間的關(guān)系厨诸,是一個(gè) NSLayoutRelationEqual 常量,包括等于禾酱、大于等于微酬、小于等于
typedef NS_ENUM(NSInteger, NSLayoutRelation) {
NSLayoutRelationLessThanOrEqual = -1, // 小于等于 <=
NSLayoutRelationEqual = 0, // 等于 =
NSLayoutRelationGreaterThanOrEqual = 1, // 大于等于 <=
};
- view2: 參照的視圖
- attr2: view2 的約束類型,是一個(gè) NSLayoutAttribute 常量颤陶,具體值和 attr1 中的列出來(lái)的一樣
- multiplier: 乘數(shù)颗管,倍數(shù)關(guān)系
- c: 常量,約束值
了解了計(jì)算公式和七個(gè)參數(shù)分別代表的意義滓走,我們可以結(jié)合蘋果給出的樣本圖加深理解:
從圖中可以看出藍(lán)色按鈕和紅色按鈕的左右間距為8垦江,根據(jù)所給出的公式我們很容易創(chuàng)建這樣一個(gè) NSLayoutConstraint 實(shí)例,創(chuàng)建的代碼如下:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self.redView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.blueView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:8.0];
約束創(chuàng)建好了搅方,接下來(lái)就是如何添加正確的添加約束了比吭。
2. 使創(chuàng)建好的約束生效
不同的約束要添加到相應(yīng)的視圖上绽族,只有添加正確約束才能生效,添加錯(cuò)誤的話程序會(huì)crash衩藤。當(dāng)然也可以直接使用屬性 active 來(lái)使約束生效吧慢,這樣不需要考慮要將約束具體添加到哪一個(gè)視圖上,但我覺(jué)得我們有必要理清楚約束的正確添加原理赏表,在實(shí)際項(xiàng)目中具體使用哪種方法使約束生效检诗,依個(gè)人喜好而定。
2.1 添加約束的原理分析及結(jié)論
添加或移除約束的涉及到的方法為:
// 添加單個(gè)約束瓢剿, 等價(jià)于接下來(lái)2.2 介紹的 constraint. active = YES.
- (void)addConstraint:(NSLayoutConstraint *)constraint NS_AVAILABLE_IOS(6_0);
// 添加多個(gè)約束逢慌, 等價(jià)于接下來(lái)2.2 介紹的 +[NSLayoutConstraint activateConstraints:].
- (void)addConstraints:(NSArray<__kindof NSLayoutConstraint *> *)constraints NS_AVAILABLE_IOS(6_0);
// 移除單個(gè)約束, 等價(jià)于接下來(lái)2.2 介紹的 constraint. active = NO.
- (void)removeConstraint:(NSLayoutConstraint *)constraint NS_AVAILABLE_IOS(6_0);
// 移除多個(gè)約束跋选, 等價(jià)于接下來(lái)2.2 介紹的 +[NSLayoutConstraint deactivateConstraints:].
- (void)removeConstraints:(NSArray<__kindof NSLayoutConstraint *> *)constraints NS_AVAILABLE_IOS(6_0);
方法介紹完涕癣,但添加到哪一個(gè)視圖上才是更需要了解的,下面主要就是分析創(chuàng)建好的約束具體要添加到哪一個(gè)視圖上前标。
實(shí)際開(kāi)發(fā)中能用storyboard設(shè)置的約束使用代碼都可以做到,而且在某些方面使用代碼設(shè)置約束反而更加靈活坠韩,為了弄清楚用代碼創(chuàng)建好的約束要在那個(gè)視圖上添加,我們這里可以分析一下使用storyboard創(chuàng)建好的約束蘋果自動(dòng)為我們添加到了哪一個(gè)視圖上炼列,通過(guò)類比只搁,可以讓我們更快速的地搞清楚這個(gè)問(wèn)題:
首先需要實(shí)現(xiàn)的頁(yè)面效果如下圖,頁(yè)面中的三個(gè)小圖從左到右依次為 View1 、View2 俭尖、View3 ,每個(gè)視圖上都包含一個(gè)屬于自己的button:
2.1.1 為view1添加不依賴于view2的約束( 如view1 的寬高約束氢惋,此時(shí) view2 = nil ,attr2 = NSLayoutAttributeNotAnAttribute )
- 分析
我們?yōu)閳D中 view1 添加寬高約束,如圖中藍(lán)色背景選中的約束所示稽犁,可以看出:為view1添加的 width約束 和 height 約束焰望,均被包含在vew1的約束下。也就是說(shuō)這兩個(gè)約束被添加到了view1上已亥。
- 結(jié)論:
在使用代碼添加約束時(shí)熊赖,如果為 view1 添加約束,該約束并不依賴于view2, 此時(shí)約束要添加到 view1上虑椎。比如常見(jiàn)的寬震鹉、高約束就是這類型約束。
2.1.2 view1 和 view2 有高低層級(jí)關(guān)系(比如為 view1 中的 button 添加依賴于view1的約束捆姜,要求 button 在 view1 上Y 方向居中传趾、X 方向居中)
-
分析:
根據(jù)上圖可以看出:button 在 view1 上 Y 方向居中、X 方向居中泥技,這兩個(gè)約束依賴的視圖有兩個(gè): 一個(gè)是 button 本身浆兰,另一個(gè)就是 button的父視圖 view1。button 和 view1 具有層級(jí)關(guān)系,view1 的層級(jí)高于button, 結(jié)合上圖藍(lán)色背景選中的約束可以發(fā)現(xiàn)創(chuàng)建好的約束依舊被添加到了在 vew1 上镊讼。
- 結(jié)論
在使用代碼添加約束時(shí)宽涌,如果為view1添加約束,該約束依賴于view2, 而且view1 和 view 2 有高低層級(jí)關(guān)系蝶棋,那么將創(chuàng)建好的約束添加到層級(jí)較高的那一個(gè)視圖上。
2.1.3 view1 和 view2 屬于同一層級(jí)忽妒, 具有相同的父視圖玩裙。(如為下圖的view1 和 view2 添加等高約束,為 view2 和 view3 添加中心點(diǎn)在Y方向上相同)
-
分析
對(duì)于view1 和 view2 的等高約束來(lái)說(shuō):他依賴于兩項(xiàng)段直,一項(xiàng)是view1, 一項(xiàng)是vew2吃溅。view2 和 view3 的中心點(diǎn)在Y方向上相同的約束, 他也依賴于兩項(xiàng)鸯檬,一項(xiàng)是view2, 一項(xiàng)是view3决侈。再看他們之間的關(guān)系:view1 和 view2 是同一個(gè)層級(jí)的兄弟視圖,view2 和 view3 也是同一個(gè)層級(jí)的兄弟視圖, 具有相同的父視圖喧务。結(jié)合上圖藍(lán)色背景選中的約束可以發(fā)現(xiàn)創(chuàng)建好的約束依舊被添加到了包含view1赖歌、view2、view3的最外層的父視圖 view 上功茴。
- 結(jié)論
在使用代碼添加約束時(shí)庐冯,如果為view1添加約束,該約束依賴于view2坎穿。而且view1 和 view 2 屬于同一層級(jí)展父,他們具有相同的父視圖,那么將創(chuàng)建好的約束添加它們的父視圖上玲昧。即:A->B->(C栖茉、D): C、D同層級(jí)孵延,約束要添加到B上
2.1.4 view1 和 view2 屬于不同的層級(jí) (如下圖的 view1 上的button 和 view2 上的button, 他們屬于不同的層級(jí)吕漂, 在這里為這兩個(gè) button 添加等寬約束)
根據(jù)上圖可以看出:view1 上的 button 和 view2 上的 button 屬于不同的層級(jí),為這兩個(gè)button所添加的這個(gè)等寬約束依賴于兩項(xiàng)隙袁,分別為這兩個(gè)button痰娱。但該約束卻被添加到了包含他們父視圖view1、view2的那個(gè)更大的父視圖 view 上菩收。
- 結(jié)論
在使用代碼添加約束時(shí)梨睁,如果為view1添加約束,該約束依賴于view2娜饵。而且view1 和 view 2 屬于不同層級(jí)坡贺,那么將創(chuàng)建好的約束添加到離它們最近的那個(gè)父視圖上。即:
A->B->E
A->C->F
E、F屬于不同層級(jí)遍坟,A是離它們最近的那個(gè)父視圖上拳亿, 所以約束要添加到A上
好了,到此為止愿伴,已經(jīng)列舉了使用代碼添加約束的幾種不同的情況肺魁,根據(jù)上面的結(jié)論就可以知道實(shí)際情況中所創(chuàng)建的約束具體要添加到哪個(gè)視圖上了。
2.2 使約束生效的另一種方式
在第2節(jié)的開(kāi)篇提到過(guò)可以使用 active 屬性這樣的方式使約束生效隔节,這不需要去區(qū)分創(chuàng)建好的約束具體要添加到那個(gè)視圖上面鹅经。這種方式主要涉及到以下三個(gè)方法:
// 通過(guò)設(shè)置該屬性的值來(lái)激活或停用一個(gè)約束,默認(rèn)值為NO
@property (getter=isActive) BOOL active NS_AVAILABLE(10_10, 8_0);
// 激活數(shù)組中的每個(gè)約束怎诫,與設(shè)置active = YES的方式相同瘾晃。這通常比單獨(dú)激活每個(gè)約束更有效。
+ (void)activateConstraints:(NSArray<NSLayoutConstraint *> *)constraints NS_AVAILABLE(10_10, 8_0);
// 停用數(shù)組中的每個(gè)約束幻妓,與設(shè)置 active = NO的方式相同蹦误。這通常比單獨(dú)停用每個(gè)約束更有效。
+ (void)deactivateConstraints:(NSArray<NSLayoutConstraint *> *)constraints NS_AVAILABLE(10_10, 8_0);
2.3 添加約束的注意事項(xiàng)
如果我們的視圖的布局方式為autolayout肉津,再添加約束之前要將視圖的translatesAutoresizingMaskIntoConstraints屬性設(shè)為NO
最好是將子視圖添加到父視圖后再添加約束强胰,例如上文的為view1中的button 添加垂直水平居中的約束,該約束是被添加到view1上的阀圾。此時(shí)要保證 button 在 view1 中,也就是 [view1 addSubview:button ];哪廓,之后再添加約束;如果不這樣做初烘,程序會(huì)crash涡真。
3.應(yīng)用
3.1 目標(biāo)實(shí)現(xiàn)效果
實(shí)現(xiàn)一個(gè)效果:頁(yè)面上有兩個(gè)視圖,一個(gè)紫色視圖肾筐,另一個(gè)是藍(lán)色視圖哆料,這兩個(gè)視圖等大,而且它們?cè)赬方向上居中顯示吗铐,紫色視圖距離頂部100东亦,藍(lán)色視圖在Y方向上距離紫色視圖的距離也是100。如下圖所示:
3.2 實(shí)現(xiàn)代碼
- (void)setViewConstraint {
UIView *purpleView = [[UIView alloc] init];
purpleView.backgroundColor = [UIColor purpleColor];
UIView *blueView = [[UIView alloc] init];
blueView.backgroundColor = [UIColor blueColor];
// 1. 禁止將 AutoresizingMask 轉(zhuǎn)換為 Constraints
purpleView.translatesAutoresizingMaskIntoConstraints = NO;
blueView.translatesAutoresizingMaskIntoConstraints = NO;
// 2. 為防止添加約束過(guò)程中程序crash,可以先將子視圖添加到父視圖中
[self addSubview:purpleView];
[self addSubview:blueView];
// 3.為purpleView添加 width 約束和 height 約束
NSLayoutConstraint *purpleViewWidthConstraint = [NSLayoutConstraint constraintWithItem:purpleView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:100];
NSLayoutConstraint *purpleViewHeightConstraint = [NSLayoutConstraint constraintWithItem:purpleView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:100];
[purpleView addConstraint:purpleViewWidthConstraint]; // 可使用purpleViewWidthConstraint.active = YES;替換
[purpleView addConstraint:purpleViewHeightConstraint]; // 可使用purpleViewHeightConstraint.active = YES;替換
// 4.為blueView添加 width 約束和 height 約束唬渗,有兩種方式典阵。第一種是可以直接類似第三步為purpleView添加寬高約束那樣添加;第二種方法是參考purpleView的寬高約束镊逝,等寬等高即可壮啊。(***注意:此時(shí)添加的約束一共有blueView 和 purpleView 兩項(xiàng),它們屬于同一層級(jí),添加約束時(shí)需要將約束添加到它們共同的父類上撑蒜,也就是self上)
NSLayoutConstraint *blueViewWidthConstraint = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:purpleView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0];
NSLayoutConstraint *blueViewHeightConstraint = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:purpleView attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0];
[self addConstraint:blueViewWidthConstraint]; // 可使用blueViewWidthConstraint.active = YES;替換
[self addConstraint:blueViewHeightConstraint]; // 可使用blueViewHeightConstraint.active = YES;替換
// 5.為purpleView添加距離頂部距離為100的頂部約束歹啼,并且在x方向上居中(***注意:此時(shí)添加的約束一共有purpleView 和 self 兩項(xiàng),self比purpleView層級(jí)要高玄渗,添加約束時(shí)需要將約束添加到層級(jí)較高的視圖上,也就是self上)
NSLayoutConstraint *purpleViewTopConstraint = [NSLayoutConstraint constraintWithItem:purpleView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:100];
NSLayoutConstraint *purpleViewCenterXConstraint = [NSLayoutConstraint constraintWithItem:purpleView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0];
[self addConstraint:purpleViewTopConstraint]; // 可使用purpleViewTopConstraint.active = YES;替換
[self addConstraint:purpleViewCenterXConstraint]; // 可使用purpleViewCenterXConstraint.active = YES;替換
// 6.為buleView添加Y方向上距離purpleView為100的約束狸眼,并且blueView在x方向上居中(***注意:根據(jù)4藤树、5中括號(hào)內(nèi)注意的描述,可知這兩個(gè)約束也是要添加到self上的)
NSLayoutConstraint *buleViewTopConstraint = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:purpleView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:100];
NSLayoutConstraint *blueViewCenterXConstraint = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0];
[self addConstraint:buleViewTopConstraint]; // 可使用buleViewTopConstraint.active = YES;替換
[self addConstraint:blueViewCenterXConstraint]; // 可使用blueViewCenterXConstraint.active = YES;替換
}