Swift中用API纯代码写autolayout

之所以在用惯了snapKit之后仍谈这个话题,是因为在很比如demo、代码段或stack overflow中回答别人等情况下,你需要使用autolayout并使它们生效,必须使用Apple的API进行编码。
我们这里不对autolayout进行基础解析,只做实际应用。分为纯API和VFL两种方式进行记录。

使用API纯代码写autolayout

Apple提供的API相对简单,不过使用上代码较长,暂且不讨论使用方便性。Apple提供的API有两套:一套是iOS9之前用的使用NSLayoutConstraint,Apple可能是因为发现了使用NSLayoutConstraint代码过长的问题,在iOS9推出了NSLayoutAnchor,不仅让约束声明更加清晰明了,而且还通过静态类型检查以确保约束能够正常工作。
1. NSLayoutConstraint
只需要创建一个NSLayoutConstraint,然后激活,添加到对应的view即可。不过,是每一个约束都要创建,所以代码较长。创建一个NSLayoutConstraint只需要一个方法,为了方便,我们对每一个参数进行注释:

NSLayoutConstraint.init(item: Any, //要约束的目标(比如 redView)

attribute: NSLayoutAttribute, //要约束的属性(比如top)

relatedBy: NSLayoutRelation, //约束类型(比如equal)

toItem: Any?,//相对于哪个目标(比如superView)

attribute: NSLayoutAttribute, //相对于这个目标的属性(比如bottom)

multiplier: CGFloat, //倍数(比如一半为0.5)

constant: CGFloat)//常数(差值,比如-10)

试验添加一个红色的view到界面上,距上距左各20,宽200,高100.

//创建一个红色的view添加到界面上
let redView = UIView()
redView.backgroundColor = .red
redView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(redView)
        
//添加距离顶部20
let topConstraint = NSLayoutConstraint.init(item: redView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 20)
topConstraint.isActive = true
        
//添加距离左边20
 let leftConstraint = NSLayoutConstraint.init(item: redView, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1.0, constant: 20)
leftConstraint.isActive = true
        
//添加宽为200
let widthConstraint = NSLayoutConstraint.init(item: redView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 200)
widthConstraint.isActive = true
        
//添加高为100
let heightConstraint = NSLayoutConstraint.init(item: redView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100)
heightConstraint.isActive = true
NSLayoutConstraint

Holy shit!这种最简单的约束,竟然需要写这么多代码,想象下你需要一个复杂界面的时候..let me die..

2. NSLayoutAnchor
iOS9之后,Apple推出了NSLayoutAnchor。NSLayoutAnchor用来创建NSLayoutConstraint对象,使用这些对象从而实现自动布局。但是一般不会直接创建NSLayoutConstraint对象,而是用UIView(NSView)或者其子类,或者UILayoutGuide的某个anchor属性(比如centerXAnchor),这些属性对应Auto Layout中主要的NSLayoutAttribute值(InterfaceBuilder下属性栏可以看到),所以也可以用NSLayoutAnchor子类创建这些NSLayoutAttribute值.
注意:UIView本身并没有提供anchor属性对应Auto Layout的margin属性,但是UILayoutGuide有这样的属性与之对应。这些属性对应Auto Layout中主要的NSLayoutAttribute值(InterfaceBuilder下属性栏可以看到),所以也可以用NSLayoutAnchor子类创建这些NSLayoutAttribute值.
使用方法也很简单:

greenView.topAnchor.constraint(equalTo: view.topAnchor, constant: 140)

看代码,语意非常清晰了已经。需要注意的是,不同的约束使用的方法(参数)不同,输入greenView.topAnchor.constraint后会有代码提示的,这里不再过多展示。
我们试试使用NSLayoutAnchor继续添加一个绿色的view到界面上,距上40,距左20,宽200,高100.

//创建一个绿色的view
let greenView = UIView()
greenView.backgroundColor = .green
greenView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(greenView)
//添加约束
greenView.topAnchor.constraint(equalTo: view.topAnchor, constant: 140).isActive = true
greenView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
greenView.widthAnchor.constraint(equalToConstant: 200).isActive = true
greenView.heightAnchor.constraint(equalToConstant: 100).isActive = true

NSLayoutAnchor

OK,Fine,代码量比去snapKit确实还是不少。需要注意的是,每一条约束,仍需要激活,可以使用NSLayoutConstraint.activate([NSLayoutConstraint])方法,也可以直接在约束语句后设置isActivetrue

3. VFL
VFL(Visual Format Language)是Apple为了缩减NSLayoutConstant代码推出的,以文本格式描述布局,可视化效果很好,比如:

H:|-20-[redView(50)]
水平方向:左边框-距离20pt-长50pt的redview。

这句话表示水平长度为50pt的redview距离左侧边框20pt。
所谓的可视化,也就可读性上,自己去写,倒要费一番功夫了。VFL的使用的方法:

NSLayoutConstraint.constraints(withVisualFormat: String, options: NSLayoutFormatOptions, metrics: [String : Any]?, views: [String : Any])

它的API短了一些,但是要凑齐参数可不是很轻便的事。参数如下:

/**
 *  VFL创建约束的API
 *
 *  @param format  传入某种格式构成的字符串,用以表达想要添加的约束,如@"H:|-margin-[redView(50)]",水平方向上,redView与父控件左边缘保持“margin”间距,redView的宽为50
 *  @param opts    对齐方式,是个枚举值
 *  @param metrics 一般传入以间距为KEY的字典,如: @{ @"margin":@20},KEY要与format参数里所填写的“margin”相同
 *  @param views   传入约束中提到的View,也是要传入字典,但是KEY一定要和format参数里所填写的View名字相同,如:上面填的是redView,所以KEY是@“redView”
 *
 *  @return 返回约束的数组
 */

咱们再用VFL试试,写一个蓝色的view到界面上,距上20,距左250,宽100,高200.

let blueView = UIView()
blueView.backgroundColor = .blue
blueView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(blueView)
//用VFL添加约束
let hVfl = "H:|-left-[blueView(100)]"
let vVfl = "V:|-top-[blueView(200)]"
let metrics = ["top":20,"left":250]
let views = ["blueView":blueView]
        
let ops = NSLayoutFormatOptions.alignAllLeft
let hConsts = NSLayoutConstraint.constraints(withVisualFormat: hVfl, options: ops, metrics: metrics, views: views)
let vConsts = NSLayoutConstraint.constraints(withVisualFormat: vVfl, options: ops, metrics: metrics, views: views)
view.addConstraints(hConsts)
view.addConstraints(vConsts)
VFL

What? 说好的更简洁呢?现在看来,Apple为了让你理解面向对象和理解布局的过程,还是煞费苦心,看来在能用massnory或者snapKit的情况,应该没人愿意使用Apple的API。
这里需要注意的是,在所有autolayout中,约束都是添加到父视图上的,如果关联的有多个视图,则约束需要添加到被约束视图的共有父视图上的。

在ScrollView中使用autolayout

在scrollview中使用autolayout时,可能稍微有些容易出错,是因为scrollView需要确定自己的contentSize,所以需要能确定子视图的大小,子视图的大小就是scrollView的contentSize。
也就是说,你需要能撑起来scrollView,且水平和竖直硬性支撑。用代表表示,就是scrollView上下左右均有约束,且子视图的宽高一定能通过约束计算出特定的大小。
用一个实例,在scrollView中添加3个view,可左右滑动,pageEnable为true。

let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.backgroundColor = .gray
scrollView.isPagingEnabled = true
view.addSubview(scrollView)
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
        
let bgColor = [UIColor.red,UIColor.green,UIColor.yellow]
        
var leftView:UIView? = nil
for i in 0..<3{
  let view = UIView()
  view.translatesAutoresizingMaskIntoConstraints = false
  view.backgroundColor = bgColor[i]
  scrollView.addSubview(view)
  if let left = leftView{
    view.leftAnchor.constraint(equalTo: left.rightAnchor, constant: 0).isActive = true
  }else{
    view.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 0).isActive = true
  }
  view.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0).isActive = true
  view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0).isActive = true
  view.widthAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: 1.0).isActive = true
  view.heightAnchor.constraint(equalTo: scrollView.heightAnchor, multiplier: 1.0).isActive = true
  leftView = view
}
//所有的view都是上左右约束到scrollView,且宽高与scrollView相同,但是scrollView右侧还没有被关联约束
//添加右侧的约束
if let left = leftView{
  left.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: 0).isActive = true
}
scrollView

如果是xib或者storyboard也拖出来遵循这个规则就好啦。再来重复一下需要满足的条件:

  1. 约束能撑起来scrollView。就是scrollView上下左右均有约束
  2. 且水平和竖直硬性支撑。所有子视图的所需的最大宽高一定能通过约束计算出特定的值
autolayout做动画

与在frame中布局不同的是,需要在animate方法中,写self.view.layoutIfNeeded(),仅此而已。
另外使用了autolayout的布局中,直接过去view的frame可能得到错误值,在didLayoutSubviews方法中再获取。

UIView.animate(withDuration: 0.3) {
    //
    self.view.layoutIfNeeded()
}

文中代码下载: [https://github.com/xueyongwei/AutpLayoutAPI]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343