iOS 關於 AutoLayout 的初級探索

AutoLayout 是什麼?

首先看字面意思就是自動佈局,恩,對,AutoLayout講的就是 iOS 中對於視圖佈局的自動佈局。那麼視圖如何選擇使用autolayout呢? 簡單地說,它是經過約束來實現的。 約束就告訴自動佈局引擎,咱們但願它在此視圖上執行佈局以及佈置視圖的方式。 那麼如今咱們瞭解一下什麼是約束。數組

Constraints(約束)

NSLayoutConstraint是繼承自NSObject的一個類,也就是用來作約束的一個類。約束是對於視圖而言的。 視圖能夠有許多約束,那麼咱們怎麼給視圖添加或者移除約束呢?bash

如今來講兩種方法給視圖添加或者移除約束:less

**第一種方法:**經過UIView的實例方法來添加和移除自身的約束:(具體看下邊 UIView 的擴展)佈局

extension UIView {
  @available(iOS 6.0, *)
  open var constraints: [NSLayoutConstraint] { get }

    
  @available(iOS 6.0, *)
  open func addConstraint(_ constraint: NSLayoutConstraint)


  @available(iOS 6.0, *)
  open func addConstraints(_ constraints:
                                     [NSLayoutConstraint]) 


  @available(iOS 6.0, *)
  open func removeConstraint(_ constraint: 
                                      NSLayoutConstraint)

  @available(iOS 6.0, *)
  open func removeConstraints(_ constraints:
                                     [NSLayoutConstraint]) 

}


複製代碼

第二種方法: 經過NSLayoutConstraint的一個類方法來給視圖添加約束,具體以下: 在看NSLayoutConstraint的實例化方法的時候,會發現他還有類方法,咱們看下面兩個方法:ui

// 添加約束
  @available(iOS 8.0, *)
  open class func activate(_ constraints:
                                      [NSLayoutConstraint])


// 移除約束
  @available(iOS 8.0, *)
  open class func deactivate(_ constraints:
                                      [NSLayoutConstraint])
複製代碼

實例化NSLayoutConstraint

咱們能夠看到以上兩種方法都須要一個NSLayoutConstraint類型的參數,如何實例化一個NSLayoutConstraint對象呢? 那麼咱們來看一下 NSLayoutConstraint 的初始化方法:spa

public convenience init(   item view1:  Any, 
                      attribute attr1:  NSLayoutAttribute,            
                   relatedBy relation:  NSLayoutRelation, 
                         toItem view2:  Any?, attribute 
                                attr2:  NSLayoutAttribute, 
                           multiplier:  CGFloat, 
                           constant c:  CGFloat)           
複製代碼

初始化方法中有不少參數,接下來咱們來說一下各個參數的意義:code

item: 能夠看到他後邊還有一個 view1 ,我的認爲通常的他就是一個 UIView 或者其子類對象,是要進行約束的那個視圖orm

attribute: 是一個NSLayoutAttribute枚舉,能夠看到他的枚舉值有 left、right、bottom、top 等,這個參數就是第一個參數中 view 所要進行的約束的位置cdn

relatedBy: 是一個NSLayoutRelation枚舉,他的枚舉值有 lessThanOrEqual(小於等於)、equal(等於)、 greaterThanOrEqual(大於等於)。 這個參數是用來指定 view1和接下來那個參數 view2兩個視圖之 間的約束關係的對象

toItem: 和第一個參數同樣,這個主要就是來和第一個 view 作參照的那個 視圖

attribute: 和第二個參數同樣,是來表示第一個視圖對第二個視圖的 參考位置 ,上下左右 仍是 center等

multiplier: 乘數的意思,CGFloat類型。是來計算兩個視圖之間位置關係的 一個重要因素

constant: 常數, CGFloat類型。也是計算兩個視圖位置關係的重要因素

這幾個參數所構造出來的NSLayoutConstraint實例,添加到視圖以後的位置關係究竟是怎樣的呢,能夠用下面 這個公式 來計算得出:

item的attribute relatedBy toItem的attribute * multiplier + constant

簡化以後就是:

A 視圖    =      B視圖  *  multiplier   +   constant;
複製代碼

公式中的等於號能夠根據 relatedBy 參數來變爲 >= 或者 <=

接下來咱們來實現添加約束

第一種方法view 的實例方法

let viewItem = UIView()
	// 必定要禁止 autoresize
	viewItem.translatesAutoresizingMaskIntoConstraints = false
	viewItem.backgroundColor = UIColor.brown
	// 必須先添加到 View上 而後再去作約束 由於約束若是要用到父視圖 不提早添加 怎麼知道誰是父視圖
	view.addSubview(viewItem)
	//  添加和父視圖view X 中心對齊 
	let centerXConstrains = NSLayoutConstraint( item: viewItem, 
                                           attribute: .centerX, 
                                           relatedBy: .equal, 
                                              toItem: view, 
                                           attribute: .centerX, 
                                          multiplier: 1, constant: 0)

	//  添加和父視圖 Y 中心對其的約束
	let centerYConstrains = NSLayoutConstraint( item: viewItem,
                                           attribute: .centerY, 
                                           relatedBy: .equal, 
                                              toItem: view, 
                                           attribute: .centerY, 
                                          multiplier: 1, 
                                            constant: 0)

	//  添加和父視圖 寬的關係 爲1:2約束
	let heightConstrains = NSLayoutConstraint( item: viewItem, 
                                          attribute: .width , 
                                          relatedBy: .equal, 
                                             toItem: view, 
                                          attribute: .width, 
                                         multiplier: 1 / 2, 
                                           constant: 0)
	//  添加自身視圖寬 高的關係 爲1:1 的約束
	let widthConstrains = NSLayoutConstraint( item: viewItem,
                                         attribute: .height , 
                                         relatedBy: .equal,
					                        toItem:  viewItem, 
				                         attribute: .width, 
				                        multiplier: 1, 
					                      constant: 0)
	// 必定要分清楚約束是相對於誰加的  加給誰的
  //		view.addConstraint(centerXConstrains)
  //		view.addConstraint(centerYConstrains)
  //		view.addConstraint(heightConstrains)
	
// 此處能夠向上邊註釋的那樣一個一個添加,也能夠以一個數組來一塊兒添加	
view.addConstraints([centerXConstrains,
                     centerYConstrains,
                      heightConstrains])
viewItem.addConstraint(widthConstrains)

複製代碼

以上代碼實現了一箇中心點在父視圖中心,寬等於父視圖寬一半的一個正方形。細心的人會看到 addConstraint方法有的是 view 調用,有的是 viewItem 調用。的確約束具體加給誰也是有原則的。

具體總結以下:

1.若是兩個視圖(也就是參數 item 和 toItem)是父子關係,設置子控件的約束,約束添加到父控件上

2.若是兩個視圖(也就是參數 item 和 toItem)是兄弟關係,設置兩兄弟的約束,約束會添加到第一個共同的父控件上

3.若是兩個視圖(也就是參數 item 和 toItem)是同一個視圖,約束會添加到本身上

使用第二種方法 :NSLayoutConstraint的類方法實現約束 在上邊咱們看了NSLayoutConstraint的類方法,很明顯他是 iOS8 以後才適用的,這個方法參數是一個NSLayoutConstraint數組。 那麼NSLayoutConstraint的實例化和上邊同樣,只是最終加給視圖約束的方法不一樣,具體實現以下:

let view1 = UIView()
view1.backgroundColor = UIColor.blue
view1.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(view1)
// view1 和 viewItem 的 Y 中心對稱		
let topContrians = NSLayoutConstraint(item: view1,       
                                 attribute: .top,  
                                 relatedBy: .equal,
                                    toItem: viewItem, 
                                 attribute: .centerY, 
                                multiplier: 1, 
                                  constant: 0)
// view1 和 viewItem 的 bottom 對其				
let bottomContrians = NSLayoutConstraint(item: view1, 
                                    attribute: .bottom,
                                    relatedBy: .equal, 
                                       toItem: viewItem, 
                                    attribute: .bottom, 
                                   multiplier: 1, 
                                     constant: 0)
// view1 和 viewItem 的 width 相等	
let widthContrains = NSLayoutConstraint(item: view1, 
                                   attribute: .width, 
                                   relatedBy: .equal,
                                      toItem: viewItem, 
                                   attribute: .width, 
                                  multiplier: 1, 
                                    constant: 0)
// view1 和 viewItem 的 leading 對齊		
let leadingContrains = NSLayoutConstraint(item: view1, 
                                     attribute: .leading, 
                                     relatedBy: .equal, 
                                        toItem: viewItem, 
                                     attribute: .leading, 
                                    multiplier: 1, 
                                      constant: 0)
// iOS8 之後 NSLayoutConstraint 的類方法 也能夠把約束添加到視圖上,並且省掉了判斷添加到那個視圖上的問題,避免了上面例子中由於視圖添加錯誤而致使的崩潰
   NSLayoutConstraint.activate([topContrians,bottomContrians,widthContrains,leadingContrains])

複製代碼

最終添加給視圖約束的方法就是這個類方法。

Anchor notation 作約束

從iOS 9開始,又一種新的方式來作約束。 其 本質也是經過上邊兩種方法給視圖添加約束,只不過是獲取NSLayoutConstraint的實例使用了Anchor,就是經過 Anchor 來實現。

如下幾個屬性就是 View 的 Anchor notation擴展,主要也是視圖的上下左右等約束點的Anchor

extension UIView {

    @available(iOS 9.0, *)
    open var leadingAnchor: NSLayoutXAxisAnchor { get }

    @available(iOS 9.0, *)
    open var trailingAnchor: NSLayoutXAxisAnchor { get }

    @available(iOS 9.0, *)
    open var leftAnchor: NSLayoutXAxisAnchor { get }

    @available(iOS 9.0, *)
    open var rightAnchor: NSLayoutXAxisAnchor { get }

    @available(iOS 9.0, *)
    open var topAnchor: NSLayoutYAxisAnchor { get }

    @available(iOS 9.0, *)
    open var bottomAnchor: NSLayoutYAxisAnchor { get }

    @available(iOS 9.0, *)
    open var widthAnchor: NSLayoutDimension { get }

    @available(iOS 9.0, *)
    open var heightAnchor: NSLayoutDimension { get }

    @available(iOS 9.0, *)
    open var centerXAnchor: NSLayoutXAxisAnchor { get }

    @available(iOS 9.0, *)
    open var centerYAnchor: NSLayoutYAxisAnchor { get }

    @available(iOS 9.0, *)
    open var firstBaselineAnchor: NSLayoutYAxisAnchor { get }

    @available(iOS 9.0, *)
    open var lastBaselineAnchor: NSLayoutYAxisAnchor { get }
}

複製代碼

能夠看到 View 的 Anchor 屬性都是 NSLayoutYAxisAnchor類型的,那麼NSLayoutYAxisAnchor是什麼呢?

@available(iOS 9.0, *)
open class NSLayoutAnchor<AnchorType : AnyObject> : NSObject {

    open func constraint(equalTo anchor: 
           NSLayoutAnchor<AnchorType>) -> NSLayoutConstraint

    open func constraint(greaterThanOrEqualTo anchor: 
           NSLayoutAnchor<AnchorType>) -> NSLayoutConstraint

    open func constraint(lessThanOrEqualTo anchor:
           NSLayoutAnchor<AnchorType>) -> NSLayoutConstraint

    open func constraint(equalTo anchor: 
            NSLayoutAnchor<AnchorType>,
                  constant c: CGFloat) -> NSLayoutConstraint

    open func constraint(greaterThanOrEqualTo anchor: 
            NSLayoutAnchor<AnchorType>,
                  constant c: CGFloat) -> NSLayoutConstraint

    open func constraint(lessThanOrEqualTo anchor: 
          NSLayoutAnchor<AnchorType>,
                  constant c: CGFloat) -> NSLayoutConstraint
}

@available(iOS 9.0, *)
open class NSLayoutXAxisAnchor:
               NSLayoutAnchor<*NSLayoutXAxisAnchor*> {
        }
複製代碼

NSLayoutXAxisAnchor就是NSLayoutAnchor的一個子類,NSLayoutAnchor有6個constraint的方法,具體看一下參數就知道有什麼區別了,這裏就不一一贅述了。每一個方法均可以返回一個 NSLayoutConstraint 類型的實例。那麼咱們就可使用返回的實例,利用上邊的兩種方法給視圖添加約束了,具體以下:(此處用的 第二種類方法)

let view2 = UIView()
view.addSubview(view2)
view2.translatesAutoresizingMaskIntoConstraints = false
view2.backgroundColor = UIColor.cyan	NSLayoutConstraint.activate(

[view2.leadingAnchor.constraint(equalTo:   
                                 view1.leadingAnchor),

view2.trailingAnchor.constraint(equalTo:
                                 view1.trailingAnchor),

view2.topAnchor.constraint(equalTo: 
                                 view1.bottomAnchor),

view2.heightAnchor.constraint(equalTo: 
                                 view1.heightAnchor)])

複製代碼

Visual format natation 來作約束

visual format 是一種基於文原本縮略的建立約束的速記(簡寫)方法 ,具備 容許同時描述多個約束的優勢,而且特別適合於當水平或垂直地佈置一系列視圖的狀況。 例子:"V:|[v2(10)]"

各個符號的意義:

V:表示正在的垂直維度; 若是是H:水平維度

|:豎線(|)表示父視圖

v2: 視圖的名稱顯示在方括號中,v2就是視圖的名稱

10:視圖名稱以後的小括號中的數字,表示視圖的大小。 V的話就是高,H 就是寬,根據視圖的排列方向而定

那麼怎麼樣使用visual format呢? 咱們來看一下NSLayoutConstraint的另外一個類方法:

open class func constraints(withVisualFormat
                         format: String, 
                   options opts: NSLayoutFormatOptions = [],
                        metrics: [String : Any]?, 
                views: [String : Any])-> [NSLayoutConstraint]
複製代碼

這個類方法最終返回了一個NSLayoutConstraint數組,那麼咱們又可使用上邊的兩個方法給視圖添加約束了

要使用visual format,必須提供一個字典,將visual format 字符串提到的每一個視圖的字符串名稱對應到實際視圖,例如:

建立兩個視圖v1和 v2:

let v1 = UIView()
		v1.backgroundColor = UIColor.blue
		v1.translatesAutoresizingMaskIntoConstraints = false
		view.addSubview(v1)
		
		let v2 = UIView()
		v2.backgroundColor = UIColor.cyan
		v2.translatesAutoresizingMaskIntoConstraints = false
		view.addSubview(v2)
複製代碼

而後就建立所謂的字典:

// 字典d 就是將字符串 v1對應實際的視圖 v1 ,"v2"對應 v2.瞭解字典的就很
   容易懂什麼意思
let d = ["v1":v1,"v2": v2]
複製代碼

具體使用咱們經過一段代碼來看:(如下代碼中註釋是對方法的第一個String 類型的參數作出的解釋)

NSLayoutConstraint.activate([

  //左右兩邊都有 | 就是說水平方向父視圖的兩邊 距離都爲零
	NSLayoutConstraint.constraints(withVisualFormat:
				"H:|[v1]|", metrics: nil, views: d), 

  // 左邊有 | 就是說豎直方向父視圖的上方  距離爲0 高度爲100
	NSLayoutConstraint.constraints(withVisualFormat:
				"V:|[v1(100)]", metrics: nil, views: d),  

  // 左邊有 | 就是說水平方向 距離父視圖的左邊爲0 且寬度爲140
	NSLayoutConstraint.constraints(withVisualFormat:
				"H:|[v2(40)]", metrics: nil, views: d),

  // 右邊有 | 就是說豎直方向距離父視圖下方爲40 高度爲120
	NSLayoutConstraint.constraints(withVisualFormat:
				"V:[v2]-(40)-|", metrics: nil, views: d), 

  // 沒有  |(豎線),也就是說這個約束和父視圖沒有關係了。
    是[v1]-0-[v2]這樣的一個形式,這說的是V(豎直)方向上v1和v2相聚爲20
	NSLayoutConstraint.constraints(withVisualFormat: 
  "V:[v1]-20-[v2]", metrics: nil, views: d)].flatMap{$0})
複製代碼

上邊這種方法不經常使用,也就不作太多的解釋了

總結

AutoLayout 就是要用約束來實現的,既然要用約束就離不開 NSLayoutConstraint這個類,最終獲得NSLayoutConstraint的類實例,而後經過上邊的兩種方法任何一種就能夠把約束添加到視圖上,完成自動佈局了。

建議仍是用類方法,出錯的機率更低一些。

佈局約束的重要計算公式:

View1    =       View2   *   multiplier   +   constant
複製代碼

若是你會 StoryBoard隨便拖進去一個控件作約束: 能夠看到約束的這個界面,一目瞭然:

約束.png

哪兩個視圖(ViewItem 和 SuperView)在作約束, 參考的是視圖的哪一個位置(CenterY), 兩個視圖之間的關係(Equal) 常量關係(Constant) 優先級 (Priority) 倍數關係 (Multiplier)

計算公式:

First Item    =       Second Item  *  Multiplier   +   Constant複製代碼
相關文章
相關標籤/搜索