codecamp
WHC_AutoLayoutKit 使用详解

前言

  
  故事从一年前说起,当时由于接到一个新项目开发任务开发之前想了想以前项目`UI`布局方式大多数都是`frame`计算有的也用到`masonry`。
  `frame`大家都知道适配各种屏幕非常繁琐各种坐标`size`计算代码很冗余后期难以维护。
  `masonry`开源给iOS开发者带来福音简化了`AutoLayout`使用方式,但是我觉得`masonry`还不足够快捷方便(有的api不知道什么意思学习成本比较高),尤其是动态布局`masonry`更新约束相当不方便,后来就决定自己开发`AutoLayout`库也就是今天**WHC_AutoLayoutKit**。
  
  在阅读之前可以先看看例子项目  

简介

  - API采用链式调用(快捷方便)一行代码搞定布局
  - 提供【Objective-C】【Swift2.3】【Swift3.0】三种语言版本库
  - 包含一行代码计算`UITableViewCell`高度模块带缓存高度
  - 包含`WHC_StackView`模块(目的替代系统`UIStackView`)
  - 隐式更新约束技术(核心后面重点介绍)

 链式调用

view.whc_Left(10)      //view与父视图左边距10
    .whc_Right(10)     //view与父视图右边距10
    .whc_Height(40)    //view自身高度40
    .whc_Top(64)       //view与父视图顶边距64

一行代码计算Cell高度

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewCell.whc_CellHeightForIndexPath(indexPath, tableView: tableView)
}

隐式更新约束

什么叫隐式更新,顾名思义就是在你添加同类型约束(可能会产生冲突约束)会自动删除前面添加可能产生冲突的约束(更新约束)看下面例子:
override func viewDidLoad() {
    super.viewDidLoad()
    let view = UIView()
    self.view.addSubview(view)


    view.whc_Left(10)       //view与父视图左边距10
        .whc_Right(10)      //view与父视图右边距10
        .whc_HeightAuto()   //view高度自动
        .whc_Top(64)        //view与父视图顶边距64
}

有时候程序执行的过程中根据需求需要动态调整UI布局假如点击按钮上面view高度调整固定64代码如下:
// 单独更新height约束
private func clickButton(sender: UIButton) {
    view.whc_Height(64)   // 只需要执行这一行代码即可更新view高度为64
}

上面这个代码执行做了什么事情呢?
他会检查view高度方向是否有同类型可能冲突约束如果检查到那么会删除上面添加的HeightAuto约束然后添加新Height固定约束64。
默认情况固定高度约束优先级比自动高度约束高所以即使不删除上面HeightAuto也没关系,但是有时候会因为约束冲突程序崩溃。再比如点击按钮如下修改:

// 单独更新top约束
private func clickButton(sender: UIButton) {
    view.whc_Top(10, toView: otherView)   //viwe 顶部间隙到otherView底部为10
}

同样上面的代码执行之前会检查y方向是否有同类型约束(可能冲突的约束),显然`view.whc_Top(10)`和`view.whc_Top(10, toView: otherView)`肯定是冲突的,所以在执行上面代码WHC_AutoLayout会先删除view的`whc_Top(10)`约束然后再添加`whc_Top(10, toView: otherView)`约束。
上面解释就是隐式更新约束技术而不需要像masonry重新重写view所有约束那么麻烦。

UIView高度自动

override func viewDidLoad() {
    super.viewDidLoad()
    let view = UIView()
    self.view.addSubview(view)

    let label = UILabel()
    self.view.addSubview(label)
    label.text = "xxxxxxxxxxxxxxxxxxxxx"
    label.whc_Left(10)      //label左到view左边距10
         .whc_Right(10)     //label右到view右边距10
         .whc_Top(10)       //label顶到view顶边距10
         .whc_HeightAuto()  //label高度自动
         .whc_Bottom(10, keepHeightConstraint: true) //label底到view底边距10,并且保留label高度
    
    view.whc_Left(10)       //view与父视图左边距10
        .whc_Right(10)      //view与父视图右边距10
        .whc_Top(64)        //view与父视图顶边距64
        .whc_HeightAuto()   //view高度自动
}

效果如下:


下面对上面代码做解释:
为什么label需要5个约束?
那是因为`view`高度需要自动根据`label`高度自动调整,而`label`高度本身是自动的如果不添加`label`与`view`的底边距`whc_Bottom`关系约束`view`无法根据`label`高度变化而变化。那可能又有人疑问?`whc_Bottom(10, keepHeightConstraint: true)`里的`keepHeightConstraint`是什么意思?前面介绍了WHC_AutoLayout是隐式更新约束技术然而很显然`label`上`whc_HeightAuto`一般情况和`whc_Bottom`是同类型约束(冲突约束)所以这两个一般情况只能存在一个约束,但是iOS有一种特殊情况需要`Height`约束和`Bottom`约束同时存在那就是在`view`自动高度的时候(`bottom`为了撑开父视图因为父视图是自动高度所以需要一个自动高度参照约束)或者`view`底边距对齐(不采用`top`对齐)的时候如:
label.whc_Left(10)      //label左到view左边距10
     .whc_Right(10)     //label右到view右边距10
     .whc_HeightAuto()  //label高度自动
     .whc_Bottom(10, keepHeightConstraint: true) //label底部间隙和父视图底部10

上面`label`就是一种从下往上布局。

总结

从上面的例子与介绍可以我们可以对**WHC_AutoLayout**得出如下结论:
- 一个控件在不使用带`keep`的约束API时候不管后面添加多少约束永远只会存在4个
- 一个控件同方向约束不管后面添加多少约束永远只会保留最后添加的同类型约束自动删除前面其他同类型约束
- 一个`view`需要高度或者宽度自动适应时候其`view`上最后一个控件需要用到5个约束(这个时候需要用到带`keep`的API,同样从下或者从右开始布局有时候也需要)
- 关于`WHC_StackView`后面会有专门的文章详细介绍

附件

1.x方向同类型约束(不会对宽度产生影响):
`Left`,`Leading`,`Trailing`,`CenterX`(包含`ToView`...)
注意WHC_AutoLayout对`Leading`和`Trailing`特殊处理理论上他们是可以成对使用的为了统一性把他们归为同类约束`Leading`左对齐`Trailing`又对齐
2.y方向同类型约束(不会对宽度产生影响):
`Top`,`BaseLineSpace`,`CenterY`(包含`ToView`...)
3.宽度方向同类型约束(对宽度产生影响):
`Width`,`Right`(包含`ToView`,自动宽度...)
4.高度方向同类型约束(对高度产生影响):
`Height`,`Bottom`(包含`ToView`,自动高度...)

WHC_AutoLayout开源地址
本人其他优秀开源项目

致敬

谢谢你的耐心阅读
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }