一、前言
说到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请留言反馈,我会及时解决。欢迎转载(在文章开头标明来源即可)。