美文网首页
Autolayout in Code

Autolayout in Code

作者: d30d9e0626b0 | 来源:发表于2016-03-07 16:31 被阅读523次

    使用代码自动布局,需求还是有的,虽然很习惯了 IB 来做。参看 Programming iOS 9。

    一共三个方法:

    • Anchor notation
    • Creating constraints in code
    • Visual format notation

    1. Anchor notation

    感觉 anchor 一个折中的方案,语法比 constraints 简洁,符合 IB 设计和添加约束的思路。美中不足是仅支持 iOS 9。

    The NSLayoutAnchor class is a factory class for creating NSLayoutConstraint objects using a fluent API. Use these constraints to programatically define your layout using Auto Layout.

    具体的使用语法都很简单,贴一个书中的 Demo:

            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)
    
            mainview.addSubview(v1)
            v1.addSubview(v2)
    
            v2.translatesAutoresizingMaskIntoConstraints = false
    
    
            var which : Int {return 3}
            switch which {
            case 1:
                // the old way, and this is the last time I'm going to show this
                v1.addConstraint(
                    NSLayoutConstraint(item: v2,
                        attribute: .Leading,
                        relatedBy: .Equal,
                        toItem: v1,
                        attribute: .Leading,
                        multiplier: 1, constant: 0)
                )
                v1.addConstraint(
                    NSLayoutConstraint(item: v2,
                        attribute: .Trailing,
                        relatedBy: .Equal,
                        toItem: v1,
                        attribute: .Trailing,
                        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)
                )
    
    
            case 2:
                // new API in iOS 9 for making constraints individually
                // and we should now be activating constraints, not adding them...
                // to a specific view
                // whereever possible, activate all the constraints at once
                NSLayoutConstraint.activateConstraints([
                    v2.leadingAnchor.constraintEqualToAnchor(v1.leadingAnchor),
                    v2.trailingAnchor.constraintEqualToAnchor(v1.trailingAnchor),
                    v2.topAnchor.constraintEqualToAnchor(v1.topAnchor),
                    v2.heightAnchor.constraintEqualToConstant(10),
                ])
    
            case 3:
                
                // NSDictionaryOfVariableBindings(v2,v3) // it's a macro, no macros in Swift
                
                // let d = ["v2":v2,"v3":v3]
                // okay, that's boring...
                // let's write our own Swift NSDictionaryOfVariableBindings substitute (sort of)
                let d = dictionaryOfNames(v1,v2,v3)
                NSLayoutConstraint.activateConstraints([
                    NSLayoutConstraint.constraintsWithVisualFormat(
                        "H:|[v2]|", options: [], metrics: nil, views: d),
                    NSLayoutConstraint.constraintsWithVisualFormat(
                        "V:|[v2(10)]", options: [], metrics: nil, views: d),
                    ].flatten().map{$0})
            default: break
            }
            
    
            
            func dictionaryOfNames(arr:UIView...) -> [String:UIView] {
        var d = [String:UIView]()
        for (ix,v) in arr.enumerate() {
            d["v\(ix+1)"] = v
        }
        return d
    }
    
    

    2. Creating constraints in code

    兼容 iOS 8 以下的,但是超级啰嗦。

    • iOS 6 可以全部使用最外面的视图添加约束,下面 Demo 中的:container.addConstraint(s) / removeConstraints
    • iOS 8 直接使用:NSLayoutConstraint.activateConstraints / deactivateConstraints
    
    import UIKit
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let container = UIView(frame: self.view.bounds)
            container.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.252461163294798)
            self.view.addSubview(container)
    
            let aLine = UIView()
            aLine.frame = CGRect(x: 0, y: 0, width: 5, height: 5)
            aLine.backgroundColor = UIColor(red: 0.6667, green: 0.0742, blue: 0.6667, alpha: 1.0)
            container.addSubview(aLine)
    
            aLine.translatesAutoresizingMaskIntoConstraints = false
    
            let i = 3
    
            switch (i) {
            case 0:
                // MARK:-  superview addConstraint
                container.addConstraint(NSLayoutConstraint(item: aLine, attribute: .Leading, relatedBy: .Equal, toItem: container, attribute: .Leading, multiplier: 1, constant: 0))
                container.addConstraint(NSLayoutConstraint(item: aLine, attribute: .Trailing, relatedBy: .Equal, toItem: container, attribute: .Trailing, multiplier: 1, constant: 0))
                container.addConstraint(NSLayoutConstraint(item: aLine, attribute: .Top, relatedBy: .Equal, toItem: container, attribute: .Top, multiplier: 1, constant: 0))
                aLine.addConstraint(NSLayoutConstraint(item: aLine, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 20))
            case 1:
                // MARK:- iOS 6
                container.addConstraints([
                    NSLayoutConstraint(item: aLine, attribute: .Leading, relatedBy: .Equal, toItem: container, attribute: .Leading, multiplier: 1, constant: 0),
                    NSLayoutConstraint(item: aLine, attribute: .Trailing, relatedBy: .Equal, toItem: container, attribute: .Trailing, multiplier: 1, constant: 0),
                    NSLayoutConstraint(item: aLine, attribute: .Top, relatedBy: .Equal, toItem: container, attribute: .Top, multiplier: 1, constant: 0),
                    NSLayoutConstraint(item: aLine, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 20),
                ])
            case 2:
                // MARK:- active = true
                if #available(iOS 8.0, *) {
                    NSLayoutConstraint(item: aLine, attribute: .Leading, relatedBy: .Equal, toItem: container, attribute: .Leading, multiplier: 1, constant: 0).active = true
                    NSLayoutConstraint(item: aLine, attribute: .Trailing, relatedBy: .Equal, toItem: container, attribute: .Trailing, multiplier: 1, constant: 0).active = true
                    NSLayoutConstraint(item: aLine, attribute: .Top, relatedBy: .Equal, toItem: container, attribute: .Top, multiplier: 1, constant: 0).active = true
                    NSLayoutConstraint(item: aLine, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 20).active = true
                } else {
                    // Fallback on earlier versions
                }
    
            case 3:
                // MARK:- NSLayoutConstraint.activateConstraints
                if #available(iOS 8.0, *) {
                    NSLayoutConstraint.activateConstraints([
                        NSLayoutConstraint(item: aLine, attribute: .Leading, relatedBy: .Equal, toItem: container, attribute: .Leading, multiplier: 1, constant: 0),
                        NSLayoutConstraint(item: aLine, attribute: .Trailing, relatedBy: .Equal, toItem: container, attribute: .Trailing, multiplier: 1, constant: 0),
                        NSLayoutConstraint(item: aLine, attribute: .Top, relatedBy: .Equal, toItem: container, attribute: .Top, multiplier: 1, constant: 0),
                        NSLayoutConstraint(item: aLine, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 20),
                    ])
                } else {
                    // Fallback on earlier versions
                }
            default:
                break
            }
        }
    
    }
    
    
    

    3. Visual format notation

    这个语法支持 iOS 6,而且语法最为简洁直观。也是
    Programming iOS 9 书中推荐的方案。实际项目尝试了上面两种两种方法后,想要更短的代码量的话,还是 Visual format notation 最为合适。这里也推荐大家,还很容易理解其语法,而且 console debugging 也会优先显示该语法。

    <NSLayoutConstraint:0x7f855ad1bb00 H:[UIButton:0x7f855ad1bba0'Button'(46@188)] priority:188>
    
    <NSLayoutConstraint:0x7f855ad1e130 UIButton:0x7f855ad1bba0'Button'.leading == UIView:0x7f855ad1ca20.leadingMargin + 127 priority:999>
    
    Unable to simultaneously satisfy constraints.
        Probably at least one of the constraints in the following list is one you don't want. 
        Try this: 
            (1) look at each constraint and try to figure out which you don't expect; 
            (2) find the code that added the unwanted constraint or constraints and fix it. 
    (
        "<NSLayoutConstraint:0x15c5ab880 V:[UIView:0x15c63d860(20)]>",
        "<NSLayoutConstraint:0x15c5abb60 V:[UIView:0x15c63d860(10)]>"
    

    当然这些都是不用 IB 和 View Debugging / Reveal 情况下的选择。

    还有个优点就是类 ASCII-art,可视化的样式描述。

    The Visual Format Language lets you use ASCII-art like strings to define your constraints. This provides a visually descriptive representation of the constraints.

    下面是一个 CustomToolBar 的 Demo:

    import UIKit
    
    /**
     Swift NSDictionaryOfVariableBindings substitute
    
     - parameter arr: UIView array: (view1, view2)
    
     - returns: return ["v1": UIView, "v2": view2]
     */
    func dictionaryOfNames(arr: UIView ...) -> [String: UIView] {
        var d = [String: UIView]()
        for (ix, v) in arr.enumerate() {
            d["v\(ix+1)"] = v
        }
        return d
    }
    
    class CustomToolBar: UIView {
    
        var textField: UITextField!
        var commentCountButton: UIButton!
        var commentImageButton: UIButton!
    
        override init(frame: CGRect) {
            super.init(frame: frame)
    
            self.backgroundColor = UIColor.whiteColor()
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override func drawRect(rect: CGRect) {
    
            let topLine = UIView()
            topLine.backgroundColor = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1.0)
            self.addSubview(topLine)
    
            textField = UITextField()
            textField.placeholder = "Write some in your deep mind."
            self.addSubview(textField)
    
            commentImageButton = UIButton(type: .Custom)
            commentImageButton.setBackgroundImage(UIImage(named: "comment"), forState: .Normal)
            self.addSubview(commentImageButton)
    
            commentCountButton = UIButton(type: .Custom)
            commentCountButton.titleLabel?.font = UIFont.systemFontOfSize(14)
            commentCountButton.setTitle("8888888888", forState: .Normal)
            commentCountButton.setTitleColor(UIColor.blackColor(), forState: .Normal)
            self.addSubview(commentCountButton)
    
            topLine.translatesAutoresizingMaskIntoConstraints = false
            textField.translatesAutoresizingMaskIntoConstraints = false
            commentImageButton.translatesAutoresizingMaskIntoConstraints = false
            commentCountButton.translatesAutoresizingMaskIntoConstraints = false
    
            // NSLayoutConstraintsHelper.swift
            let d = dictionaryOfNames(topLine, textField, commentImageButton, commentCountButton)
    
            self.addConstraints([
                NSLayoutConstraint.constraintsWithVisualFormat("H:|[v1]|", options: [], metrics: nil, views: d),
                NSLayoutConstraint.constraintsWithVisualFormat("V:|[v1(0.5)]", options: [], metrics: nil, views: d),
    
                NSLayoutConstraint.constraintsWithVisualFormat("H:|-15-[v2]-[v3(14)][v4]-|", options: .AlignAllCenterY, metrics: nil, views: d),
                NSLayoutConstraint.constraintsWithVisualFormat("V:|-[v2]-|", options: [], metrics: nil, views: d),
                NSLayoutConstraint.constraintsWithVisualFormat("V:[v3(14)]", options: [], metrics: nil, views: d),
                NSLayoutConstraint.constraintsWithVisualFormat("V:[v4(14)]", options: [], metrics: nil, views: d),
            ].flatten().map { $0 })
        }
    }
    
    
    

    总结

    经过几次项目的实践,发现还是 Visual Format 最好用,简洁直观。

    参考

    相关文章

      网友评论

          本文标题:Autolayout in Code

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