NSLayoutConstraint的使用


*一切皆代碼*
- --

#繼承關系
框架|類|類
:-:|:-:|:-:
UIKit|NSLayoutConstraint|-
-|-|-

#應用場景
UI界面的搭建一般會占用項目開發相當一部分的時間。涉及到控件布局,控件配置,人機交互,動畫效果,數據顯示,屏幕適配6個方面,還要考慮視覺效果,性能體驗,數據邊界(沒有數據/很多數據),操作防御(各種狂點)4個方面。另外,UI界面也是開發過程中需求變化比較多的地方。 其中控件布局是最基礎和根本的一個方面。

布局,顧名思義,就是設置控件在屏幕上的位置。自動是相對之前的手動計算控件坐標(frame)布局而言的。NSLayoutConstraint描述某控件與其他控件的位置關系,再由布局引擎在恰當的時候自動計算出該控件的坐標,進而准確的顯示到屏幕上。這種位置關系稱為約束。當某些變化發生時,布局引擎會根據這些約束自動的調整控件的位置大小。

這些變化主要來自兩個方面:
外因:屏幕的不同尺寸,屏幕的旋轉,父視圖的改變,用戶行為和視圖邏輯
內因:顯示的內容(text,image),字體的調整

目前,除了原生的NSLayoutConstraint,GitHub上比較主流的自動布局框架還有masonry和SDAutoLayout。masonry相當於原生框架的封裝,語法更簡潔。SDAutoLayout提供了cell高度自適應等便捷方法。

# 概念原理
- 坐標frame
屏幕采用的是二維坐標系,原點在左上角(0.0),向右x坐標增大,向下y坐標增大。靠近原點偏移時偏移量為負,遠離原點偏移時偏移量為正。坐標單位是點point,也是代碼中的布局單位。

- 約束constraint
一條約束相當於一個線性方程:
item1.attribute1 [=,<=,>=] multiplier * item2.attribute2 + constant
一條約束包含5種元素:視圖item,屬性attribute,關系relationship,乘因子multiplier,常量constant。一個視圖有水平垂直兩個方向,每個方向需要兩個約束確定,即四個約束確定一個視圖的位置大小。約束多了,沖突;約束少了,歧義,都會無法正常顯示甚至crash。

-  屬性attribute
描述位置:top,bottom,left,right,centerX,centerY,leading,trailing
描述尺寸:wight,height
水平方向:left,right,centerX,wight,leading,trailing
垂直方向:top,bottom,centerY,height

- 關系relation
相當關系 =
不等關系 >=,<=

- 乘因子multiplier
倍數關系

- 常量constant
偏移量,注意偏移方向和正負值

- 優先級priority
用來描述約束的優先級。數值是1-1000。1000表示約束是必須滿足的,其他值表示可選的。約束的優先級默認是必須的(1000)。系統定義了一些字符常量來表示必須,高,低,適應尺寸等優先級,由高到低。不應該顯示使用必須優先級,不應該隨意指定優先級數值,常用取值999,750,500,250,左右浮動一兩個點。盡量使用系統定義的字符常量。布局引擎統籌全局,將首先滿足必須的約束。可選約束,能滿足就滿足,不能滿足也會盡量靠近。所以,不能滿足的可選約束也會影響最后的顯示效果。

- 固有內容尺寸intrinsic content size
固有內容尺寸可以理解為默認尺寸。一個控件的內容尺寸由有無顯示的內容,內容內邊距,可否滑動,有無外在約束決定。核心就是顯示的內容。一般能顯示內容的控件都有內容尺寸。控件根據內容自適應,父視圖根據子視圖布局自適應都基於這個概念。具有固有內容尺寸的控件,可以不約束寬或高從而直接使用固有內容尺寸。

控件|固有內容尺寸
:-:|:-:
UIView|沒有固有尺寸
UILabel|寬高都有固有尺寸
UIButton|寬高都有固有尺寸
UITextField|寬高都有固有尺寸
UISwitch|寬高都有固有尺寸,且應該使用固有尺寸(不要約束寬高)
UIImageView|無內容時沒有固有尺寸,有內容后,寬高都有固有尺寸
UITextView|若能滑動,沒有;若不能滑動,默認以將字符串單行顯示時的size為固有尺寸;若不能滑動,且約束了寬度,則自動調整高度,來顯示全部字符串,以此時的size作為固有尺寸。

- CHCR約束
CHCR是一對針對固有尺寸的約束,有固有尺寸的視圖水平,垂直兩個方向都有一對CHCR約束。使用CHCR需要管理其優先級。
CH,content hugging,內容抱緊(壓縮)
相當於view.width <= 0.0 * nil.NotAnAttribute + intrinsicwidth
CR,content compression resistance,內容壓縮阻尼(伸展)
相當於view.width >= 0.0 * nil.NotAnAttribute + intrinsicwidth

- 固有內容尺寸 intrinsic content size 與 視圖size自適應
當有一個視圖supview,使用constraint布局它的subviews。若subviews之間的約束鏈能明確的確定它們占用的size,就相當於supview的intrinsic content size確定了,系統會自動的計算出intrinsic content size作為supview的size。這時,只需要對supview添加約束,確定其位置就可以了。這種邏輯在水平或垂直某個方向上也是成立的。這就是視圖自適應的原理。父視圖自適應三個關鍵點:
1 . 子視圖不要以父視圖自適應的那個邊為參照。若父視圖寬度自適應,就不要以右邊為參照,若父視圖高度自適應,就不要以底邊為參照。
2 . 子視圖的約束鏈要能明確的確定這些子視圖的總的寬(水平方向)或高(水平方向)
3 . 若父視圖是寬度自適應,需要添加一個與最右邊的子視圖的約束;若父視圖是高度自適應,需要添加一個與最下邊的子視圖的約束(我簡稱為兜底)


# 代碼流程
- 布局的具體流程
1 . 創建控件(alloc,init或new)
2 . 添加到父視圖(addSubview)
3 . 配置控件屬性
4 . 關閉轉換(setTranslatesAutoresizingMaskIntoConstraints)
5 . 創建約束(一般每個控件4個約束)
6 . 激活約束(添加約束到最近公共父視圖)
7 . 更新約束/取消激活約束(移除約束)

- 添加子視圖
```
[self.view addSubviews:@[_scroll]];
```
- 關閉轉換
```
[_scroll setTranslatesAutoresizingMaskIntoConstraints:NO];
```
- 創建約束
```
NSLayoutConstraint * scroll_top = [NSLayoutConstraint constraintWithItem:_scroll
                                                                   attribute:NSLayoutAttributeTop
                                                                   relatedBy:NSLayoutRelationEqual
                                                                      toItem:self.view
                                                                   attribute:NSLayoutAttributeTop
                                                                  multiplier:1.0f
                                                                    constant:0];
NSLayoutConstraint * scroll_left = [NSLayoutConstraint constraintWithItem:_scroll
                                                                    attribute:NSLayoutAttributeLeft
                                                                    relatedBy:NSLayoutRelationEqual
                                                                       toItem:self.view
                                                                    attribute:NSLayoutAttributeLeft
                                                                   multiplier:1.0f
                                                                     constant:0];
NSLayoutConstraint * scroll_botton = [NSLayoutConstraint constraintWithItem:_scroll
                                                                      attribute:NSLayoutAttributeBottom
                                                                      relatedBy:NSLayoutRelationEqual
                                                                         toItem:self.view
                                                                      attribute:NSLayoutAttributeBottom
                                                                     multiplier:1.0f
                                                                       constant:0];
NSLayoutConstraint * scroll_right = [NSLayoutConstraint constraintWithItem:_scroll
                                                                     attribute:NSLayoutAttributeRight
                                                                     relatedBy:NSLayoutRelationEqual
                                                                        toItem:self.view
                                                                     attribute:NSLayoutAttributeRight
                                                                    multiplier:1.0f
                                                                      constant:0];

```
特別的:
```
NSLayoutConstraint * top = [NSLayoutConstraint constraintWithItem:view
                                                                   attribute:NSLayoutAttributeWight
                                                                   relatedBy:NSLayoutRelationEqual
                                                                      toItem:nil
                                                                   attribute:NSLayoutAttributeNotAnAttribute
                                                                  multiplier:0.0
                                                                    constant:100];
```
- 添加約束
注意:約束必須添加到所有涉及到的item的最近公共父視圖。
```
[self.view addConstraints:@[scroll_top,scroll_left,scroll_botton,scroll_right]];
```
或者(推薦)
```
[NSLayoutConstraint  activateConstraints:@[scroll_top,scroll_left,scroll_botton,scroll_right]];
```
- 移除約束
```
[self.view removeConstraints:@[scroll_top,scroll_left,scroll_botton,scroll_right]];
```
或者(推薦)
```
[NSLayoutConstraint  deactivateConstraints:@[scroll_top,scroll_left,scroll_botton,scroll_right]];
```
- 更新約束
改變約束的情況:
改變約束的activate(相當於添加/移除)
改變約束的constant(主要,直接在需要改變的地方某個已有約束的constant 屬性即可)
改變約束的priority
移除視圖(移除視圖會移除與之相關的所有約束)
- 相關方法
view的方法
```
[view updateConstraintsIfNeeded];//立即更新view及其子視圖的約束
[view layoutIfNeeded];//立即更新view及其子視圖的位置尺寸
```
```
[view setNeedsUpdateConstraints];//延時更新約束,系統自動對所有VC調用updateViewConstraints方法,對所有View調用updateConstraints方法;VC 重寫updateViewConstraints方法,View重寫updateConstraints。例:
- (void)updateConstraints{
//這里設置約束
[super updateConstraints];//必須在后面調用父類該方法。
}

[view setNeedsLayout];//延時更新位置尺寸,系統自動對所有VC調用viewWillLayoutSubviews方法,對所有view調用layoutSubviews方法。重寫layoutSubviews方法,例:
- (void)layoutSubviews{
//這里最后設置坐標frame(不建議)
[super layoutSubviews];//必須在后面調用父類該方法。
}
```
```
CGSize size = [view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];//根據約束返回size
CGSize size = [view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize  withHorizonalFittingPriority:UILayoutPriorityFittingSizeLevelverticalFittingPriority:UILayoutPriorityFittingSizeLevel];//根據約束,水平垂直優先級返回size
```
```
[view setContentHuggingPriority:UILayoutPriorityFittingSizeLevel forAxis:UILayoutConstraintAxisHorizontal];//設置某個方向的CH優先級
[view setContentCompressionResistancePriority:UILayoutPriorityFittingSizeLevel forAxis:UILayoutConstraintAxisHorizontal];//設置某個方向的CR優先級
```
```
[view sizeThatFit:currentbounds];//不修改view的size,返回view的size
[view sizeToFit:currentbounds];//調用sizeThatFit方法,會修改view的size,返回view的size。
```
label的方法
```
label.numberOfLines = 0;//顯示不限行數,label高度自適應需要
label.preferredMaxLayoutWidth = 180;//label高度自適應需要
```
```
CGFloat height = label.font.lineHeight;//label的單行高度
```
```
CGRect titleRect = [titleLabel.text boundingRectWithSize:CGSizeMake([UIScreen mainScreen].bounds.size.width - 20, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16]} context:nil];//根據text,font等信息返回label的size
```
# 使用原則
- 創建約束
1. 先添加到父視圖,后添加約束(先添加后約束)
2. size屬性(wight,height)不能對location屬性(top,bottom,left,right,centerX,centerY)
水平屬性(left,right,centerX)不能對垂直屬性(top,bottom,centerY)。height可對wight。
3. left/right不能對leading/trailing
4. 不能只將constant分配給location(top,botton,left,right),centerX,centerY可以。
5. locaton(top,botton,left,right)中的multiplier必須為1.0。centerX,centerY不必。

- 添加約束
1.必須添加到約束涉及到的所有item的最近父視圖。若用下面方法添加,系統自動添加的正確的視圖。
```
[NSLayoutConstraint  activateConstraints:@[scroll_top,scroll_left,scroll_botton,scroll_right]];
```

- CHCR
1. 當一列view填充一個空間,若每個view有相同的CH優先級,系統將不知道拉伸哪個viw。此時,應將需要拉伸的view的CH優先級降低。實際,在IB中,每個UILable的CH優先級會自動設為251。
2. 對於背景不可見的view,如lable,button。它們經常會被莫名其妙的拉伸,導致顯示的內容的位置稍有偏差。為防止這種情況,將其CH的優先級升高。
3. 有些視圖,如swith,就應該按照intrinsec content size 顯示,將其CH,CR的優先級都升高。
4. 盡量不要給CHCR優先級設為required(1000)。若想讓一個view按照它的intrinsic content size顯示,將它的CHCR優先級設為(999)。

# 代碼demo
- label的寬度自適應
```
```
- label的高度自適應
```
```
- 父視圖size固定的多個label子視圖
- 父視圖高度自適應的多個label子視圖
- 父視圖寬度自適應的多個label子視圖
- 父視圖size自適應的多個label子視圖
- scrollview的contentsize自適應
- tableviewcell的高度自適應

# 報錯記錄
>錯誤描述:
錯誤碼:
代碼環境:
原因:
解決方案:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM