1、Views - AutoLayout

作者: smalldu | 来源:发表于2016-05-12 11:51 被阅读503次

    AutoLayout不是一个必须要使用的技术,你可以选择使用它或者不适用它。

    一个View可以通过以下三种方式来使用AutoLayout:

    • 在代码中给view添加constraint(约束)
    • 你的app使用xib并勾选了"Use Auto Layout"
    • 自定义一个继承自UIView的view,将requiresConstraintBasedLayout: 方法返回true

    🌰

    class MyView: UIView {
        
        override func updateConstraints() {
            super.updateConstraints()
            print("执行")
        }
        
        override class func requiresConstraintBasedLayout()->Bool{
            return true
        }
    }
    

    你可以在 updateConstraints 方法中使用代码创建约束。但是如果AutoLayout没有开放,这个方法并不会被调用。


    Constraints ( 约束 )

    一个AutoLayout的约束是一个NSLayoutConstraint的实例 , 用来描述view自身的宽高或者与别的view的属性的关系。这两个view要拥有同一个superview(不一定是直接父视图)

    来看看NSLayoutConstraint 的属性:

    • firstItem, firstAttribute, secondItem, secondAttribute

      一个约束用来描述两个view的属性的关系,如果约束是描述一个view自己的height或者width,那么second view的值就是nil,它的attribute就是.NotAnAttribute 。 另外NSLayoutAttribute有以下值:

      • .Top, .Bottom
      • .Left, .Right, .Leading, .Trailing
      • .Width, .Height
      • .CenterX, .CenterY
      • .FirstBaseline, .LastBaseline

      .Leading, .Trailing 等价于 .Left , .Right
      .FirstBaseline主要用于多个labels,指从上到下有一定距离。.LastBaseline是指从下到上。

    • multiplier, constant
      用来描述属性的关系,multiplier做乘法,constant做加法
      关系表达式:a1=m*a2+c,默认a1 = 1 * a2+0
      比如要写一个v的宽度是v1的宽度的0.5被多3

      NSLayoutConstraint(item: v,
                             attribute: .Width,
                             relatedBy: .Equal,
                             toItem: v1,
                             attribute: .Width,
                             multiplier: 0.5,
                             constant: 3)
      
    • relation
      是一个NSLayoutRelation类型的值,表示两个属性之间的关系,有三种关系,分别是:LessThanOrEqual Equal、GreaterThanOrEqual。上面的例子也有用到。

    • priority (优先级)
      priority的值从1000-1,每个约束可以有不同的优先级,决定他们使用的顺序。

    约束是作用在view上的,一个view可以有多个约束,so view有一个constraints 属性。还有一些实例方法:

    • addConstraint:, addConstraints:
    • removeConstraint:, removeConstraints:

    一般需要把约束添加在superview上

    NSLayoutConstraint属性除过 priority 和 constant 其他都是只读的,所以如果你想改变已经存在的约束的其他属性,你必须先remove掉这个约束,然后再添加一个新的。


    Autoresizing constraints

    AutoLayout会根据我们写的约束在运行时决定view的frame和autoresizingMask属性的设置。

    如果你准备对你的view使用明确的约束,不需要自动去计算的,记得将translatesAutoresizingMaskIntoConstraints 属性设置为false,如果你没有这么做,可能会引起冲突。


    Creating constraints in code(在代码中使用约束)

    一般会使用NSLayoutConstraint的初始化方法 init(item:attribute:relatedBy:toItem:attribute: multiplier:constant:) 需要设置基本所需的属性

    将上节最后的那个布局用约束试试,我们给v1使用frame,v2、v3使用AutoLayout , 给 v2、v3 的translatesAutoresizingMaskIntoConstraints属性设置为false

    
    let mainview = self.view
    let v1 = UIView(frame:CGRectMake(100, 111, 132, 194))
    v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
    let v2 = UIView()
    v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
    let v3 = UIView()
    v3.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
    mainview.addSubview(v1)
    v1.addSubview(v2)
    v1.addSubview(v3)
    v2.translatesAutoresizingMaskIntoConstraints = false
    v3.translatesAutoresizingMaskIntoConstraints = false
    
    v1.addConstraint(
        NSLayoutConstraint(item: v2,
            attribute: .Left,
            relatedBy: .Equal,
            toItem: v1,
            attribute: .Left,
            multiplier: 1, constant: 0)
    )
    
    v1.addConstraint(
        NSLayoutConstraint(item: v2,
            attribute: .Right,
            relatedBy: .Equal,
            toItem: v1,
            attribute: .Right,
            multiplier: 1, constant: 0)
    )
    
    v1.addConstraint(
        NSLayoutConstraint(item: v2,
            attribute: .Top,
            relatedBy: .Equal,
            toItem: v1,
            attribute: .Top,
            multiplier: 1, constant: 0)
    )
    
    v2.addConstraint(
        NSLayoutConstraint(item: v2,
            attribute: .Height,
            relatedBy: .Equal,
            toItem: nil,
            attribute: .NotAnAttribute,
            multiplier: 1, constant: 10)
    )
    
    v3.addConstraint(
        NSLayoutConstraint(item: v3,
            attribute: .Width,
            relatedBy: .Equal,
            toItem: nil,
            attribute: .NotAnAttribute,
            multiplier: 1, constant: 20)
    )
    
    v3.addConstraint(
        NSLayoutConstraint(item: v3,
            attribute: .Height,
            relatedBy: .Equal,
            toItem: nil,
            attribute: .NotAnAttribute,
            multiplier: 1, constant: 20)
    )
    
    v1.addConstraint(
        NSLayoutConstraint(item: v3,
            attribute: .Right,
            relatedBy: .Equal,
            toItem: v1,
            attribute: .Right,
            multiplier: 1, constant: 0)
    )
    
    v1.addConstraint(
        NSLayoutConstraint(item: v3,
            attribute: .Bottom,
            relatedBy: .Equal,
            toItem: v1,
            attribute: .Bottom,
            multiplier: 1, constant: 0)
    )
    
    

    v1和v2相关的约束都加载v1上 , v1和v3相关的约束也都加在v1上。代码量虽然多了,但是复杂度并不高,可读性也变高了。随便改变v1的frame,v2、v3都会自己适应。

    效果:

    配图
    Anchor notation

    iOS9开始我们可以使用一些简短紧凑的语法来描述AutoLayout,我们把关注点从约束转移到了描述约束关系的属性。
    以下列出view的一些属性:

    • topAnchor, bottomAnchor
    • leftAnchor, rightAnchor, leadingAnchor, trailingAnchor
    • centerXAnchor, centerYAnchor
    • firstBaselineAnchor, lastBaselineAnchor

    这些属性都是NSLayoutAnchor的对象(或者它的子类).

    处理relation的一些方法:

    • constraintEqualToConstant:
    • constraintGreaterThanOrEqualToConstant:
    • constraintLessThanOrEqualToConstant:
    • constraintEqualToAnchor:
    • constraintGreaterThanOrEqualToAnchor:
    • constraintLessThanOrEqualToAnchor:
    • constraintEqualToAnchor:constant:
    • constraintGreaterThanOrEqualToAnchor:constant:
    • constraintLessThanOrEqualToAnchor:constant:
    • constraintEqualToAnchor:multiplier:
    • constraintGreaterThanOrEqualToAnchor:multiplier:
    • constraintLessThanOrEqualToAnchor:multiplier:
    • constraintEqualToAnchor:multiplier:constant:
    • constraintGreaterThanOrEqualToAnchor:multiplier:constant:
    • constraintLessThanOrEqualToAnchor:multiplier:constant:

    有了这些,前面的8条约束,可以写成下面这样:

    NSLayoutConstraint.activateConstraints([
                    v2.leftAnchor.constraintEqualToAnchor(v1.leftAnchor),
                    v2.rightAnchor.constraintEqualToAnchor(v1.rightAnchor),
                    v2.topAnchor.constraintEqualToAnchor(v1.topAnchor),
                    v2.heightAnchor.constraintEqualToConstant(10),
                    v3.widthAnchor.constraintEqualToConstant(20),
                    v3.heightAnchor.constraintEqualToConstant(20),
                    v3.rightAnchor.constraintEqualToAnchor(v1.rightAnchor),
                    v3.bottomAnchor.constraintEqualToAnchor(v1.bottomAnchor)
                ])
    
    

    运行效果一样的,但是代码变的非常简洁了有木有。可惜从iOS 9 开始支持呀。。不过幸亏还有SnapKit库。


    Visual format notation

    更简写的方式,来看一个表达式:

    "V:|[v2(10)]"
    

    V: vertical(垂直)方向的尺寸。H:水平方向
    | 在这里表示它的superview , 这里表示v2的顶部贴着它superview的顶部。小括号中的10表示v2的高度是10.
    我们需要提前指定那个字符串代表那个view

    所以我们约束表达式也可以这么写:

    let d = ["v2":v2,"v3":v3]
            
    NSLayoutConstraint.activateConstraints([
            NSLayoutConstraint.constraintsWithVisualFormat(
            "H:|[v2]|", options: [], metrics: nil, views: d),
            NSLayoutConstraint.constraintsWithVisualFormat(
            "V:|[v2(10)]", options: [], metrics: nil, views: d),
            NSLayoutConstraint.constraintsWithVisualFormat(
            "H:[v3(20)]|", options: [], metrics: nil, views: d),
            NSLayoutConstraint.constraintsWithVisualFormat(
            "V:[v3(20)]|", options: [], metrics: nil, views: d)
        ].flatten().map{$0})
    

    运行效果也是一样的。前面用了8行,这里只有四行。

    下面看下更多关于这个语法的东西:

    • metrics 参数是一个放numeric值的字典,允许你用name解释一个numeric数值
    • options: 是一个NSLayoutFormatOptions类型的值,主要做alignments。
    • 指定两个view之间的距离可以用这种语法"[v1]-20-[v2]"
    • 括号中的值也可以指定符号"[v1(>=20@400,<=30)]"

    等多语法的只是可以在Apple’s Auto Layout Guide 的 “Visual Format Syntax” 章节学习


    Constraints as objects

    有时候界面需要变换,比如一个view移动到另一个地方,一个view移除,或者新增一个view,这时候我们需要事先把布局保存起来。

    看下面的例子:

    首先创建两个数组保存约束,并声明三个全局变量

    var constraintsWith = [NSLayoutConstraint]()
    var constraintsWithout = [NSLayoutConstraint]()
    var v1:UIView!
    var v2:UIView!
    var v3:UIView!
    

    我们首先展示的是v1\v2\v3三个view纵向排列。然后移除v2将v3的位置移动到v2。 也可以还原。

    初始化信息:

    let mainview = self.view
    let v1 = UIView()
    v1.backgroundColor = UIColor.redColor()
    v1.translatesAutoresizingMaskIntoConstraints = false
    let v2 = UIView()
    v2.backgroundColor = UIColor.yellowColor()
    v2.translatesAutoresizingMaskIntoConstraints = false
    let v3 = UIView()
    v3.backgroundColor = UIColor.blueColor()
    v3.translatesAutoresizingMaskIntoConstraints = false
    mainview.addSubview(v1)
    mainview.addSubview(v2)
    mainview.addSubview(v3)
    
    self.v1 = v1
    self.v2 = v2
    self.v3 = v3
    
    let c1 = NSLayoutConstraint.constraintsWithVisualFormat(
        "H:|-(20)-[v(100)]", options: [], metrics: nil, views: ["v":v1])
    let c2 = NSLayoutConstraint.constraintsWithVisualFormat(
        "H:|-(20)-[v(100)]", options: [], metrics: nil, views: ["v":v2])
    let c3 = NSLayoutConstraint.constraintsWithVisualFormat(
        "H:|-(20)-[v(100)]", options: [], metrics: nil, views: ["v":v3])
    let c4 = NSLayoutConstraint.constraintsWithVisualFormat(
        "V:|-(100)-[v(20)]", options: [], metrics: nil, views: ["v":v1])
    
    let c5with = NSLayoutConstraint.constraintsWithVisualFormat(
        "V:[v1]-(20)-[v2(20)]-(20)-[v3(20)]", options: [], metrics: nil,
        views: ["v1":v1, "v2":v2, "v3":v3])
    
    let c5without = NSLayoutConstraint.constraintsWithVisualFormat(
        "V:[v1]-(20)-[v3(20)]", options: [], metrics: nil,
        views: ["v1":v1, "v3":v3])
    
    self.constraintsWith.appendContentsOf(c1)
    self.constraintsWith.appendContentsOf(c2)
    self.constraintsWith.appendContentsOf(c3)
    self.constraintsWith.appendContentsOf(c4)
    self.constraintsWith.appendContentsOf(c5with)
    
    self.constraintsWithout.appendContentsOf(c1)
    self.constraintsWithout.appendContentsOf(c3)
    self.constraintsWithout.appendContentsOf(c4)
    self.constraintsWithout.appendContentsOf(c5without)
    
    NSLayoutConstraint.activateConstraints(self.constraintsWith)
    

    这段代码很明显,先设置了c1、c2、c3、c4 设置了他们的水平宽度和距离superview左边的20 。

    c5with 是有v2的情况
    c5without是没有v2的情况

    然后搞一个按钮在点击的时候进行切换:

    if v2.superview != nil {
        v2.removeFromSuperview()
        NSLayoutConstraint.deactivateConstraints(self.constraintsWith)
        NSLayoutConstraint.activateConstraints(self.constraintsWithout)
    } else {
        self.view.addSubview(v2)
        NSLayoutConstraint.deactivateConstraints(self.constraintsWithout)
        NSLayoutConstraint.activateConstraints(self.constraintsWith)
    }
    

    效果:

    配图
    Guides and margins

    一个view紧挨着顶部或者底部,有时候这个顶部或者底部是可以隐藏和显示的,那么这个距离怎么算呢。为了解决这个问题,UIViewController提供了两个不可见的view在AutoLayout中使用起来很方便。topLayoutGuidebottomLayoutGuide , topLayoutGuide匹配最顶部的bar,bottomLayoutGuide匹配最底部的bar。这两个view会随环境的改变而改变。

    我们在visual format语法中也能用

    let arr = NSLayoutConstraint.constraintsWithVisualFormat(
    "V:[tlg]-0-[v]", options: [], metrics: nil,
    views: ["tlg":self.topLayoutGuide, "v":v])
    

    在iOS 9中也可以这样:

    let tlg = self.topLayoutGuide
    let c = v.topAnchor.constraintEqualToAnchor(tlg.bottomAnchor)
    

    在iOS 8 中,我们可以获取到UIView的layoutMargins是一个UIEdgeInsets,在VC中我获取到mainView的是:

    UIEdgeInsets(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0)
    

    这个是你可能需要你的view距离superview边界的最小距离。
    可以通过不指名距离来使用margin

    let arr = NSLayoutConstraint.constraintsWithVisualFormat(
    "H:|-[v]", options: [], metrics: nil, views: ["v":v])
    

    我自己写了个例子:

    let mainview = self.view
    let v = UIView()
    v.translatesAutoresizingMaskIntoConstraints = false
    v.backgroundColor = UIColor.redColor()
    mainview.addSubview(v)
    let c1 = NSLayoutConstraint.constraintsWithVisualFormat(
        "H:|-[v]-|", options: [], metrics: nil, views: ["v":v])
    let c2 = NSLayoutConstraint.constraintsWithVisualFormat(
        "V:[tlg]-[v(200)]-[blg]", options: [], metrics: nil, views: ["v":v,"tlg":self.topLayoutGuide,"blg":self.bottomLayoutGuide])
    NSLayoutConstraint.activateConstraints(
        [c1,c2].flatten().map{$0}
    )
    

    运行效果:

    配图

    一个view的margin可以用以下方式表示:

    • .TopMargin, .BottomMargin
    • .LeftMargin, .RightMargin, .LeadingMargin, .TrailingMargin
    • .CenterXWithinMargins, .CenterYWithinMargins

    因此,我们还可以这样创建一个约束

    
    let c = NSLayoutConstraint(item: v,
                               attribute: .Left,
                               relatedBy: .Equal,
                               toItem: mainview,
                               attribute: .LeftMargin,
                               multiplier: 1,
                               constant: 0)
    

    或者 这样

    let c = v.leftAnchor.constraintEqualToAnchor(
                mainview.layoutMarginsGuide.leftAnchor)
    

    Intrinsic content size and alignment rects (内建大小和对其方式)

    一些iOS内置的控件本身有内建大小(一个方向或者两个方向)。
    如:

    • UIButton , 默认有个高度,宽度依赖于它的title
    • UIImageView , 默认会适应它image的大小
    • UILabel , 如果宽度固定,高度可以显示多行,高度自己根据文字适应

    内建size会隐式产生约束。这个约束是一个低优先级的约束。如果没有别的相关约束阻止它,才会执行。

    下面看两个view的方法:

    • contentHuggingPriorityForAxis:
      阻止它自己在某个方向变大,比如有两个label在同一行紧挨着。那么两个文字都过长的时候会显示这个优先级.一般默认值是250
    v.contentHuggingPriorityForAxis(UILayoutConstraintAxis.Horizontal)
    v.setContentHuggingPriority(1000, forAxis: UILayoutConstraintAxis.Horizontal)
    

    第一个是获取值,第二个是设置。第二个参数是方向。(水平/垂直)

    • contentCompressionResistancePriorityForAxis:
      阻止变小,默认值750.跟上面那个相反,用法一样
    v.contentHuggingPriorityForAxis(UILayoutConstraintAxis.Horizontal)
    v.setContentCompressionResistancePriority(1000, forAxis: UILayoutConstraintAxis.Horizontal)
    

    大概就这些,后面是Stack views暂时不打算看。通过interface Builder 拖拽约束,网上例子很多。

    相关文章

      网友评论

        本文标题:1、Views - AutoLayout

        本文链接:https://www.haomeiwen.com/subject/koderttx.html