一、前言
說到Swift中對AutoLayout的封裝王者無疑是SnapKit,它簡單方便的調用無疑深得人心。筆者今天要講的是利用Swift的特性對AutoLayout進行簡單的封裝。相比較SnapKit而言,它的使用不能說更簡單方便,但實現可以說是一目了然,也算是對AutoLayout的介紹。下面將會講封裝的思路以及使用介紹。
二、JYAutoLayout的封裝思路
如上面演示圖的所示,所有橙色 和 白色的View的布局均相對于灰色的大View 而每個view的布局只需要短短的一行代碼:
UIedgeView(htrBtn).left(centerBtn,c:8).alignTop(centerBtn).size(btnSize).end()
2.1NSLayoutConstraint約束創建
convenience init(item view1: AnyObject, attribute attr1: NSLayoutAttribute, relatedBy relation: NSLayoutRelation, toItem view2: AnyObject?, attribute attr2: NSLayoutAttribute, multiplier: CGFloat, constant c: CGFloat)
這是一條約束的創建,它的約束關系是 view1.attr1 < = > view2.attr2 * multiplier + c
所以我們可以看到決定一個View1的某條約束所需的參數有:
var View2 : UIView? 相對于哪個view 即view2
var Attribute1 : NSLayoutAttribute? view1 的NSLayoutAttribute
var Attribute2 : NSLayoutAttribute? view2 的NSLayoutAttribute
var Multiplier : CGFloat? 比例系數
var Equal = NSLayoutRelation.Equal 大于 等于 小于
var offset : CGFloat 偏移
var priority : UILayoutPriority 當然還有當前約束的優先級
2.2NSLayoutAttribute 包含有
case Left //左側
case Right //右側
case Top //上方
case Bottom //下方
case Leading //首部
case Trailing //尾部
case Width //寬度
case Height //高度
case CenterX //X軸中心
case CenterY //Y軸中心
case Baseline //文本底標線
case NotAnAttribute //沒有屬性
在iOS8.0下又多了 一些邊界屬性
case LeftMargin
case RightMargin
case TopMargin
case BottomMargin
case LeadingMargin
case TrailingMargin
case CenterXWithinMargins
case CenterYWithinMargins
注:Left/Right 和 Leading/Trailing的區別是Left/Right永遠是指左右,而Leading/Trailing在某些從右至左習慣的地區會變成,leading是右邊,trailing是左邊。
2.3簡化封裝
以view的上邊為例我們可以提供下面一個方法來表示一條約束參數
private func top(v:UIView! , c : CGFloat , a : NSLayoutAttribute = NSLayoutAttribute.Bottom , m : CGFloat = 1.0 , e : NSLayoutRelation = NSLayoutRelation.Equal, p : UILayoutPriority = UILayoutPriorityDefaultHigh) -> UIedgeView {
}
view的top 與 v 的 a * m + c 當然還有優先級
swift有個非常好的地方,那就是只要設置了默認參數就可以不用傳該參數,而在我們實際使用中 一般都是 等于 而比例細數也基本 是1.0,所以通常 只要設置 v(view)c (偏移) a(對應的邊)即可,而優先級也基本不使用。
又考慮到 top 對 top 與 top 對 Bottom 是極為常見的所以又以方法名來區分 以 alignTop方法來表示 top 對 top關系 而 top方法默認表示 top 對 Bottom關系 當然top方法仍可以傳入NSLayoutAttribute參數,這樣%90的情況下我們只需要設置兩個參數 view 與 c 就可以描述一條參數。如: htrBtn.left(centerBtn,c:8)
同理對 Bottom Right Left Width Height CenterX CenterY做了處理。
2.4鏈式編程的實現
swift的方法調用都是點語法,再也不像OC那樣需要[],我們只需要在一個方法結束時返回self 就可以無限調用
@discardableResult func top(_ v: UIView! , c: CGFloat = 0 , a: NSLayoutAttribute = NSLayoutAttribute.bottom , m: CGFloat = 1.0 , e: NSLayoutRelation = NSLayoutRelation.equal, p: UILayoutPriority = UILayoutPriorityDefaultHigh) -> UIedgeView {
let layout = JYlayout(v: v, c: c, a1:NSLayoutAttribute.top , a2: a, m: m, e: e, p: p)
dict .setValue(layout, forKey: ffTop)
return self
}
2.5約束的添加 end() remake() update()
對于這么一個布局 htrBtn.left(centerBtn,c:8).alignTop(centerBtn).size(btnSize) 都是約束的準備只是將約束所需要的參數保存到了UIedgeView這么一個對象中
而 end() remake() update() 才是添加相應的約束
end() 什么都不管只管添加約束
remake() 會將之前的約束全部刪除,重新添加
update() 會根據當前代碼所設計到的約束,而刪除對應約束,比如 btn.centerX(self).size(100, h: 100).update() 它涉及到了 centerX Width Height 的約束那么它會將之前添加的關于 centerX Width Height 的所有約束刪除然后添加。
注意:1.remake() update() 刪除約束使用的是以下方法:
public func removeConstraint(constraint: NSLayoutConstraint) // This method will be deprecated in a future release and should be avoided. Instead set NSLayoutConstraint's active property to NO.
這個方法將會在將來的版本中被棄用,應該避免。蘋果也不太建議刪除約束再添加,如果有約束改變應當記錄約束直接修改,可以參考Demo中的AnimDemoView1,或者添加多個約束使用修改優先級來達到改變的目的,可以參考Demo中的AnimDemoView2
2.不要使用 btn.centerX(self).centerX(view2).end() 這種寫法是錯誤的,有效約束只會是centerX(view2)。如有需要應當如下:
btn.makeConstraint { (make) in
make.centerX(reference1,p:priorityMedium).end()
make.centerX(reference2,p:priorityHigh).end()
}
7.提供的方法說明
top 默認 top 對 bottom
alignTop top 對 top
left 默認 left 對 right
alignLeft left 對 left
bottom 默認 bottom 對 top
alignBottom bottom 對 bottom
right 默認 right 對 left
alignRight right 對 right
centerX 默認 centerX 對 centerX
centerY 默認 centerY 對 centerY
center 默認 centerX 對 centerX centerY 對 centerY
height 有對view 的 height 也有提供 數字 50
width 有對view 的 width 也有提供 數字 50
size 有對view 的 width height 也有提供 CGsize
方法的參數描述
@discardableResult func top(_ v: UIView! , c: CGFloat = 0 , a: NSLayoutAttribute = NSLayoutAttribute.bottom , m: CGFloat = 1.0 , e: NSLayoutRelation = NSLayoutRelation.equal, p: UILayoutPriority = UILayoutPriorityDefaultHigh) -> UIedgeView {
let layout = JYlayout(v: v, c: c, a1:NSLayoutAttribute.top , a2: a, m: m, e: e, p: p)
dict .setValue(layout, forKey: ffTop)
return self
}
v :參照的view
c : 偏移
m : 比例系數
a : 參照view的NSLayoutAttribute
e : NSLayoutRelation.Equal 大于 等于 小于
p : 優先級
關于優先級定義:
public let priorityHighTop = (UILayoutPriority)(UILayoutPriorityDefaultHigh + 1);
public let priorityHigh = UILayoutPriorityDefaultHigh;
public let priorityMedium = (UILayoutPriority)(500);
public let priorityLow = UILayoutPriorityDefaultLow;
public let priorityRequired = UILayoutPriorityRequired;
public let priorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;
如果還需要 Baseline 等相關關系可自行封裝,都是體力活
三、結尾
JYAutoLayout是筆者在Swift2 時代的一個練手產物,利用了Swift點語法調用以及參數可預設的特性對AutoLayout的封裝,使用也是簡單方便。SnapKit雖然使用簡單,但在我看來它封裝的過于復雜了,帶來的收益(簡單調用)完全可以用更方便的方式封裝。相信大家在升級到Xcode8時看到SnapKit爆紅時有多么萌比,我們也只能等待作者更新。與其依賴別人,不如依賴自己,JYAutoLayout的封裝相當簡單相信一個對AutoLayout有所了解的同學在看完筆者的簡單說明后自己就可以寫出來了,在之上擴展優化更不在話下。
如果我的文章對你有幫助或者給了你一些啟發,希望你能在github給個小星星,如果你在使用過程中遇到了Bug請留言反饋,我會及時解決。歡迎轉載(在文章開頭標明來源即可)。