AutoLayout 使用详解

2,859 阅读5分钟

前言


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

在阅读之前可以先看看例子项目:github.com/netyouli/WH…

简介


  • 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会先删除viewwhc_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高度本身是自动的如果不添加labelview的底边距whc_Bottom关系约束view无法根据label高度变化而变化。那可能又有人疑问?whc_Bottom(10, keepHeightConstraint: true)里的keepHeightConstraint是什么意思?前面介绍了WHC_AutoLayout是隐式更新约束技术然而很显然labelwhc_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_AutoLayoutLeadingTrailing特殊处理理论上他们是可以成对使用的为了统一性把他们归为同类约束Leading左对齐Trailing又对齐
2.y方向同类型约束(不会对宽度产生影响):
Top,BaseLineSpace,CenterY(包含ToView...)
3.宽度方向同类型约束(对宽度产生影响):
Width,Right(包含ToView,自动宽度...)
4.高度方向同类型约束(对高度产生影响):
Height,Bottom(包含ToView,自动高度...)


WHC_AutoLayout开源地址github.com/netyouli/WH…
本人其他优秀开源项目:github.com/netyouli/

致敬

谢谢你的耐心阅读

掘金征文: juejin.cn/post/684490…