前言:
在面前已經(jīng)寫過一篇有關(guān)AutoLayout
的東西了(項目干貨挖掘4——如何優(yōu)雅地使用AutoLayout自動布局)蛤签。這幾天在看《iOS Auto Layout開發(fā)秘籍》這本書辞友,又撈到了些干貨。所以作此篇震肮,用于補充。
約束:
約束是什么戳晌?
AutoLayout
是給相應(yīng)的視圖添加“約束”來進行布局的鲫尊。所謂“約束”Constraint
,就是有關(guān)視圖布局的限定躬厌。
有關(guān)約束的類
NSLayoutConstraint
马昨,唯一一個共有的,我們可以直接操作的類扛施,所謂自動布局時給視圖添加約束就是使用該類鸿捧;
NSContentSizeLayoutConstraint
,內(nèi)容大小約束疙渣,私有匙奴。像UILabel
和UIImageView
有內(nèi)容大小,我們可以指定視圖尺寸和內(nèi)容大小的規(guī)則妄荔,比如內(nèi)容吸附規(guī)則盡量避免添加補白泼菌,而內(nèi)容壓縮規(guī)則防止內(nèi)容被剪切谍肤。這個類就是用來處理這個的。
NSAutoresizingMaskLayoutConstrain
哗伯,自動尺寸調(diào)整約束荒揣,私有。該類將自動尺寸調(diào)整掩碼轉(zhuǎn)換成AutoLayout
系統(tǒng)中對應(yīng)的約束焊刹。
_UILayoutSupportConstraint
系任,布局支持約束,私有虐块。iOS 7新增的約束俩滥,它用來建立視圖控制器實例頂部和底部的實際邊界。布局支持約束防止視圖的內(nèi)容與狀態(tài)欄之類的障礙物重疊贺奠。
NSIBPrototypingLayoutConstraint
霜旧,原型約束,私有儡率。iOS 7新增的約束挂据,它是Interface Builder(IB)
為你添加的約束。
在這些約束中儿普,我們直接可以使用的只有NSLayoutConstraint
這個共有類棱貌,但是其他這幾個私有的約束類型,我們也要了解一下箕肃。因為當(dāng)布局有問題時婚脱,在控制器的日志里,會打印出布局時有問題的約束信息勺像,即約束的類名障贸,通過類名,我們可以快速明白哪里出了問題吟宦。
約束應(yīng)該添加在哪個視圖上篮洁?
約束添加在該約束所引用幾個視圖的最近公共祖先中。
約束的優(yōu)先級:
@property UILayoutPriority priority;
typedef float UILayoutPriority;
static const UILayoutPriority UILayoutPriorityRequired NS_AVAILABLE_IOS(6_0) = 1000; // A required constraint. Do not exceed this.
static const UILayoutPriority UILayoutPriorityDefaultHigh NS_AVAILABLE_IOS(6_0) = 750; // This is the priority level with which a button resists compressing its content.
static const UILayoutPriority UILayoutPriorityDefaultLow NS_AVAILABLE_IOS(6_0) = 250; // This is the priority level at which a button hugs its contents horizontally.
static const UILayoutPriority UILayoutPriorityFittingSizeLevel NS_AVAILABLE_IOS(6_0) = 50; // When you send -[UIView systemLayoutSizeFittingSize:], the size fitting most closely to the target size (the argument) is computed. UILayoutPriorityFittingSizeLevel is the priority level with which the view wants to conform to the target size in that computation. It's quite low. It is generally not appropriate to make a constraint at exactly this priority. You want to be higher or lower.
優(yōu)先級在NSLayoutConstraint
中殃姓,以priority
屬性來修改袁波,它是UILayoutPriority
類型的,而該類型其實是對float
的再定義蜗侈,也就是說優(yōu)先級其實是浮點類型的篷牌,范圍是從1到1000。
蘋果為我們提供了幾個優(yōu)先級枚舉踏幻,UILayoutPriorityRequired
的優(yōu)先級為1000枷颊,表示這是一個必需執(zhí)行的優(yōu)先級;UILayoutPriorityDefaultHigh
的優(yōu)先級為750,表示這是一個抵抗壓縮阻力的優(yōu)先級夭苗,假如我們設(shè)置某Label
尺寸的約束優(yōu)先級為751信卡,且尺寸小于Label
的內(nèi)容大小,則會執(zhí)行尺寸的優(yōu)先級题造,因為它的優(yōu)先級更高傍菇,更迫切。這樣的話會造成Label
被剪切了界赔;UILayoutPriorityDefaultLow
的優(yōu)先級為250桥嗤,表示這是一個抵抗拉伸阻力的優(yōu)先級。
蘋果建議我們采用更靈活的數(shù)字來給約束設(shè)置優(yōu)先級仔蝌,而少用枚舉的幾個值。
內(nèi)容大小荒吏,壓縮阻力敛惊,拉伸阻力:
** 內(nèi)容大小:**
對于像UILabel
和UIImageView
之類的有內(nèi)容的視圖绰更,一般都有內(nèi)容大小intrinsicContentSize
瞧挤,即不用你指定該視圖的尺寸大小,它的尺寸默認(rèn)是內(nèi)容大小儡湾,內(nèi)容有多大特恬,它就顯示多大。對于普通的徐钠,無內(nèi)容的視圖癌刽,intrinsicContentSize
則默認(rèn)是是(-1,-1)
。無內(nèi)容的視圖尝丐,intrinsicContentSize
的getter
方法返回(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric)
显拜,若你想讓其有內(nèi)容大小,則可以子類化該視圖爹袁,重寫intrinsicContentSize
的getter
方法远荠。
- (CGSize)intrinsicContentSize
{
return CGSizeMake(30, 30);
}
** 壓縮阻力和拉伸阻力:**
壓縮阻力是為了防止視圖內(nèi)容被剪切,拉伸阻力是為了防止視圖內(nèi)容被擴大失息。決定壓縮阻力或拉伸阻力是否能夠成功的是你設(shè)置的內(nèi)容大小的約束優(yōu)先級是多少譬淳。以壓縮阻力來說,前面提到過盹兢,枚舉值UILayoutPriorityDefaultHigh
表示的就是壓縮阻力的優(yōu)先級邻梆,即750。若你給視圖設(shè)置的保持內(nèi)容大小的約束優(yōu)先級大于750绎秒,則說明保持視圖內(nèi)容大小更迫切确虱,則即使設(shè)置的尺寸約束比內(nèi)容大小要小,也不會對視圖進行剪切,因為壓縮阻力起作用了校辩。用代碼來寫就是這樣:
CustomView *customView = [CustomView new];
// 壓縮阻力
[customView setContentCompressionResistancePriority:751 forAxis:UILayoutConstraintAxisHorizontal];
[customView setContentCompressionResistancePriority:751 forAxis:UILayoutConstraintAxisVertical];
// 拉伸阻力
[customView setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
[customView setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical];
約束的沖突:P131頁
在添加可能最常遇見的問題是不充分的約束和有沖突的約束窘问。不充分的約束就是指約束不足以將視圖確定,有沖突的約束就是給視圖添加的約束邏輯有沖突宜咒。這兩種情況都會導(dǎo)致最終界面顯示出現(xiàn)意外惠赫,很可能在界面上看不到該視圖。此時故黑,就需要你發(fā)現(xiàn)有問題的約束儿咱,做出修改調(diào)整了。
一般來說出現(xiàn)約束沖突有兩種可能性场晶。首先第一種就是我們沒有關(guān)閉視圖的“自動尺寸調(diào)整”屬性混埠,“自動尺寸調(diào)整”autoresizingMask
和AutoLayout
本身并不沖突。兩者其實是可以同時使用的诗轻,只要兩者所約束的邏輯不沖突钳宪,則不會影響界面布局。而且前面我們也提到了NSAutoresizingMaskLayoutConstrain
這個類扳炬,它是將自動尺寸調(diào)整的東西轉(zhuǎn)換成了AutoLayout
中對應(yīng)的約束吏颖。
默認(rèn)情況下,視圖是沒有關(guān)閉“自動尺寸調(diào)整”屬性的恨樟,若我們打算在項目中統(tǒng)一使用AutoLayout
來進行界面布局的話半醉,就得將其關(guān)閉。代碼如下:
CustomView *customView = [CustomView new];
customView.translatesAutoresizingMaskIntoConstraints = NO;
自動布局的動畫劝术;
若你的界面是以frame
進行布局的缩多,則在進行UIView
動畫時,對frame
進行相應(yīng)的修改就行了养晋。那若你的界面是AutoLayout
的瞧壮,在UIView
動畫時該怎樣執(zhí)行動畫呢?其實和修改frame
一樣匙握,需要我們在執(zhí)行動畫時對約束進行調(diào)整咆槽。
[UIView animateWithDuration:1.f animations:^{
[_frontView removeAllConstraint]; // 移除原有的約束,開始重新添加約束
LAY(_frontView.left, _bottomView.left, 1, 0);
LAY(_frontView.centerY, _bottomView.centerY, 1, 0);
LAY(_frontView.height, _bottomView.height, 1, 0);
LAY(_frontView.width, _bottomView.width, 0.9, 0);
[self.contentView layoutIfNeeded]; // 立即重新布局
}];
關(guān)于layoutIfNeeded
方法圈纺,詳情請見:UIView的layoutSubviews秦忿、layoutIfNeeded、setNeedsLayout區(qū)別和聯(lián)系
滾動視圖的自動布局:
如果用frame
布局蛾娶,滾動視圖的用法我們已經(jīng)非常熟悉了灯谣。創(chuàng)建scrollView
并設(shè)置其frame
,然后設(shè)置contentSize
蛔琅,這個屬性表示scrollView
可滾動的區(qū)域胎许,有了該屬性設(shè)置的區(qū)域,滑動scrollView
時,才會在有限的frame
區(qū)域中滑出所有的視圖辜窑。這個contentSize
屬性是必不可少的钩述,不然不會顯示scrollView
上的子視圖。
_scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 100, PDWidth_mainScreen, 300)];
_scrollView.backgroundColor = [UIColor grayColor];
_scrollView.contentSize = CGSizeMake(PDWidth_mainScreen*4, 0);
[self.contentView addSubview:_scrollView];
for(int i=0; i<4; i++)
{
UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(i*PDWidth_mainScreen, 0, PDWidth_mainScreen, 300)];
subView.backgroundColor = PDColor_Random;
[_scrollView addSubview:subView];
}
但如果穆碎,我們將frame
布局牙勘,直接替換為AutoLayout
布局。雖然打印出來的contentSize
是正常的所禀,但是程序跑起來后發(fā)現(xiàn)根本就沒subView
顯示:
_scrollView = [UIScrollView create];
_scrollView.backgroundColor = [UIColor grayColor];
_scrollView.contentSize = CGSizeMake(PDWidth_mainScreen*4, 0);
[self.contentView addSubview:_scrollView];
LAY(_scrollView.left, self.contentView.left, 1, 0);
LAY(_scrollView.right, self.contentView.right, 1, 0);
LAY(_scrollView.centerY, self.contentView.centerY, 1, 0);
LAYC(_scrollView.height, 300);
for(int i=0; i<4; i++)
{
UIView *subView = [UIView create];
subView.backgroundColor = PDColor_Random;
[_scrollView addSubview:subView];
LAY(subView.top, _scrollView.top, 1, 0);
LAY(subView.bottom, _scrollView.bottom, 1, 0);
LAYC(subView.width, self.contentView.bounds.size.width);
if(i==0){
LAY(subView.left, _scrollView.left, 1, 0);
}else{
UIView *preView = _scrollView.subviews[i-1];
LAY(subView.left, preView.right, 1, 0);
}
}
NSLog(@"??????:scrollView.contentSize = %@", NSStringFromCGSize(_scrollView.contentSize));
這篇文章對此問題做些較詳細的解說(史上最簡單的UIScrollView+Autolayout出坑指南)方面,我直接給出正確的方案吧。
可以看到色徘,我們建了個containerView
放在了scrollView
上恭金,然后將多個子視圖是添加在這個containerView
上的,而并非直接添加在scrollView
上褂策。而且最最重要的是有關(guān)containerView
的布局約束横腿,containerView
的top
,left
,bottom
,right
和scrollView
的top
,left
,bottom
,right
的間距均為0。普通情況下我們只需要這四個約束就已足夠辙培,因為可以將一個視圖的布局固定了。但是這里邢锯,我們之所以創(chuàng)建containerView
添加在scrollView
上扬蕊,其實是為了正確地算出滾動的范圍,所以也要給containerView
添加width
和height
的約束丹擎,這個是關(guān)鍵尾抑!
_scrollView = [UIScrollView create];
_scrollView.backgroundColor = PDColor_Name_Black;
// _scrollView.contentSize = CGSizeMake(PDWidth_mainScreen*4, 0);
_scrollView.pagingEnabled = YES;
[self.contentView addSubview:_scrollView];
LAY(_scrollView.left, self.contentView.left, 1, 0);
LAY(_scrollView.right, self.contentView.right, 1, 0);
LAY(_scrollView.centerY, self.contentView.centerY, 1, 0);
LAYC(_scrollView.height, 150);
UIView *containerView = [UIView create];
containerView.backgroundColor = PDColor_Orange;
[_scrollView addSubview:containerView];
LAY(containerView.top, _scrollView.top, 1, 0);
LAY(containerView.left, _scrollView.left, 1, 0);
LAY(containerView.bottom, _scrollView.bottom, 1, 0);
LAY(containerView.right, _scrollView.right, 1, 0);
LAYC(containerView.width, PDWidth_mainScreen*4);
LAYC(containerView.height, 150);
for(int i=0; i<4; i++)
{
UIView *subView = [UIView create];
subView.backgroundColor = PDColor_Random;
[containerView addSubview:subView];
LAY(subView.top, containerView.top, 1, 0);
LAY(subView.bottom, containerView.bottom, 1, 0);
LAYC(subView.width, PDWidth_mainScreen);
if(i==0){
LAY(subView.left, containerView.left, 1, 0);
}else{
UIView *preV = containerView.subviews[i-1];
LAY(subView.left, preV.right, 1, 0);
}
}
NSLog(@"??????:scrollView.contentSize = %@", NSStringFromCGSize(_scrollView.contentSize));
注意:在上面的代碼中,我們沒有給contentSize
賦值蒂培,所以contentSize
打印出的值為(0,0)
如何封裝一個AutoLayout庫:
見項目干貨挖掘4——如何優(yōu)雅地使用AutoLayout自動布局
對AutoLayout
的封裝以及Demo
再愈,我上傳至GitHub
了(YWAutoLayout
GitHub地址)。Demo
效果如下:
如何在xCode設(shè)置护戳,使其在模擬器上顯示自動布局的邊線:
Xcode
——>Debug
——>View Debugging
——>Show View Frames
或者Show Alignments Rectangles
翎冲。