你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> AutoLayout2

AutoLayout2

編輯:IOS開發綜合

iOS 開發中 Storyboard 的運用非常的常見,所以通過代碼設置 AutoLayout 的場景會比較少。但是不代表沒有。

需要純代碼構建界面(公司要求,創建 UI 控件) 需要根據屏幕方向或數據內容提供不同的約束方案。

可是蘋果提供的 Autolayout 語法真是要讓人抓狂的節奏。於是衍生了很多類庫,如

Stevia Masonry …

另外還有 MyLinearLayout ,這是基於 frame 的自動布局方案。

每一個方案都有自己不同的解決方法,有側重於代碼直觀可讀的,有側重於代碼簡潔的。我自己比較喜歡能讓代碼變得更加簡潔,當然這是在保證可讀性的情況下的。

所以我就這一塊進行比較深入的了解,並造一個輪子作為自己的學習成果。

背景知識

NSLayoutConstraint

NSLayoutConstraint(item view1: AnyObject,
              attribute attr1: NSLayoutAttribute,
           relatedBy relation: NSLayoutRelation,
                 toItem view2: AnyObject?,
              attribute attr2: NSLayoutAttribute,
        multiplier multiplier: CGFloat,
                   constant c: CGFloat)

整個輪子就圍繞著這一個核心方法來進行。

基礎屬性
item: 需要添加約束的對象 attr1: 對 item 進行約束的邊 relation: 約束關系,大於小於等於 toItem: 約束的相對對象,可以是父視圖或其他兄弟視圖 attr2: 相對對象的約束邊 multiplier: 位置計算的倍數參數 constant: 位置計算的常數參數 其他屬性
priority: 約束的優先級。 active: 約束是否生效 identify: 可以給約束添加標識符 約束的計算式
item.attr1 = toItem.attr2 * multiplier + constant

鏈式編程

將多個相關操作通過 (.) 號連接起來,增加代碼塊的邏輯性以及可讀性。最簡單的實現辦法,就是將函數的返回值設置為返回對象本身。

自定義操作符

設置自定義操作符
infix operator +-: AdditionPrecedence

操作符的位置(前中後綴) operator 操作符: 操作符優先級組

extension Vector2D {
    static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y - right.y)
    }
}

在 Swift 3 之前,操作符的運算還只能添加到全局, Swift 3 已經可以添加到類當中了,不過需要用 static 前綴。(那麼還是一個全局……但是這樣的改進對代碼的易讀性會更好,畢竟一看就知道這是有關於哪一個類的運算。)

不過差別還是有的,添加到類當中的運算必須要有該類作為參數之一。這其實還是很好理解其中的邏輯的,畢竟跟它沒關系你添加到它裡面干嘛呢……

Apple: The Swift Programming Language(Swift 3) -> Language Guide -> Advanced Operators -> Custom Operators

操作符優先級

蘋果的標准操作符等級
Swift Standard Library Operators

添加自定義操作符等級

precedencegroup precedence group name {
higherThan: lower group names
lowerThan: higher group names
associativity: associativity
assignment: assignment
}

Apple: The Swift Programming Language(Swift 3) -> Language Peference -> Declarations -> Precedence Group Declaration

思考

要點

進行 AutoLayout 的時候,有幾個元素是需要考慮清楚的。

Who
給哪個對象設置約束。 相對於哪個對象。 約束添加到那一個對象中。(一般是父視圖) Where
設置的約束是哪一條邊。 相對哪一條邊。 When
什麼時候進行添加?

方案

對此我做了兩方面的嘗試:

鏈式:通過鏈式給視圖添加約束,大概是這樣
Layout(view, item, second).leading().top().trailing().bottom()
重寫操作符:通過操作符直觀的表達約束關系
Layout(view).auto {
    item.top == second.top * 1.5 + 10
    ...
}

兩者的優缺點都有吧,鏈式可以極大的縮短代碼的長度,操作符可以直觀的“看到”約束是怎麼樣的,而且也比較好玩,但是代碼量卻不少。到底是更短的代碼,還是更易讀的代碼……這是一個需要好好平衡的問題。

實現

偽代碼

Property
weak var view: UIView weak var first: UIView weak var second: UIView 鏈式 Methods
輔助方法
視圖變化類方法:用於設置三個屬性 約束獲取方法:用於獲取設置之後的約束,以留著後期用於動畫等操作。 設置方法
完全自定義的方法 設置視圖尺寸的方法 設置 first 和 second 單邊約束的方法 常用的雙邊方法 常用的多邊方法 操作符 Methods
class View 用於進行操作符運算的新類型,保存約束所需要的信息。
Property
weak var super: UIView? weak var view: UIView! var attribute: NSLayoutAttribute var constant: CGFloat = 0 var multiplier: CGFloat = 1 var priority: UILayoutPriority? Init 重寫操作符
== <= >= : 添加運算 / : 設置 multiplier : 設置 constant >>> : 設置 priority 擴展 UIView 添加相應約束的屬性,造成可以直接 UIView 設置約束的錯覺。

實現

//
//  Layout.swift
//  Layout
//
//  Created by 黃穆斌 on 16/9/24.
//  Copyright ? 2016年 MuBinHuang. All rights reserved.
//

import UIKit

// MARK: - Layout

class Layout {

    // MARK: Views

    weak var view: UIView!
    weak var first: UIView!
    weak var second: UIView!

    // MARK: Init

    init(_ view: UIView) {
        self.view = view
    }

    // MARK: Set View

    func view(_ first: UIView) -> Layout {
        self.first = first
        self.first.translatesAutoresizingMaskIntoConstraints = false
        if self.second == nil {
            self.second = self.view
        }
        return self
    }

    func to(_ second: UIView) -> Layout {
        self.second = second
        return self
    }

    // MARK: Constraints

    var constrants: [NSLayoutConstraint] = []

    @discardableResult
    func get(layouts: ([NSLayoutConstraint]) -> Void) -> Layout {
        layouts(constrants)
        return self
    }

    @discardableResult
    func clear() -> Layout {
        constrants.removeAll(keepingCapacity: true)
        return self
    }

    // MARK: Custom Edge Methods

    @discardableResult
    func layout(edge: NSLayoutAttribute, to: NSLayoutAttribute? = nil, constant: CGFloat = 0, multiplier: CGFloat = 1, priority: UILayoutPriority = UILayoutPriorityDefaultHigh, related: NSLayoutRelation = .equal) -> Layout {
        let layout = NSLayoutConstraint(item: first, attribute: edge, relatedBy: related, toItem: second, attribute: to ?? edge, multiplier: multiplier, constant: constant)
        layout.priority = priority
        view.addConstraint(layout)
        constrants.append(layout)
        return self
    }

    @discardableResult
    func equal(edges: NSLayoutAttribute...) -> Layout {
        for edge in edges {
            let layout = NSLayoutConstraint(
                item: first,
                attribute: edge,
                relatedBy: .equal,
                toItem: second,
                attribute: edge,
                multiplier: 1,
                constant: 0
            )
            view.addConstraint(layout)
            constrants.append(layout)
        }
        return self
    }

    // MARK: - Size Edge Methods

    @discardableResult
    func size(height: CGFloat, priority: UILayoutPriority = UILayoutPriorityDefaultHigh) -> Layout {
        let layout = NSLayoutConstraint(
            item: first,
            attribute: .height,
            relatedBy: .equal,
            toItem: nil,
            attribute: .notAnAttribute,
            multiplier: 1,
            constant: height
        )
        layout.priority = priority
        first.addConstraint(layout)
        constrants.append(layout)
        return self
    }

    @discardableResult
    func size(width: CGFloat, priority: UILayoutPriority = UILayoutPriorityDefaultHigh) -> Layout {
        let layout = NSLayoutConstraint(
            item: first,
            attribute: .width,
            relatedBy: .equal,
            toItem: nil,
            attribute: .notAnAttribute,
            multiplier: 1,
            constant: width
        )
        layout.priority = priority
        first.addConstraint(layout)
        constrants.append(layout)
        return self
    }

    @discardableResult
    func size(ratio: CGFloat, constant: CGFloat = 0, priority: UILayoutPriority = UILayoutPriorityDefaultHigh) -> Layout {
        let layout = NSLayoutConstraint(
            item: first,
            attribute: .width,
            relatedBy: .equal,
            toItem: first,
            attribute: .height,
            multiplier: ratio,
            constant: constant
        )
        layout.priority = priority
        first.addConstraint(layout)
        constrants.append(layout)
        return self
    }

    // MARK: - One Edge Methods

    @discardableResult
    func leading(_ constant: CGFloat = 0, multiplier: CGFloat = 1, relate: NSLayoutRelation = .equal, priority: UILayoutPriority = UILayoutPriorityDefaultHigh) -> Layout {
        let layout = NSLayoutConstraint(
            item: first,
            attribute: .leading,
            relatedBy: relate,
            toItem: second,
            attribute: .leading,
            multiplier: multiplier,
            constant: constant
        )
        layout.priority = priority
        view.addConstraint(layout)
        constrants.append(layout)
        return self
    }

    @discardableResult
    func trailing(_ constant: CGFloat = 0, multiplier: CGFloat = 1, relate: NSLayoutRelation = .equal, priority: UILayoutPriority = UILayoutPriorityDefaultHigh) -> Layout {
        let layout = NSLayoutConstraint(
            item: first,
            attribute: .trailing,
            relatedBy: relate,
            toItem: second,
            attribute: .trailing,
            multiplier: multiplier,
            constant: constant
        )
        layout.priority = priority
        view.addConstraint(layout)
        constrants.append(layout)
        return self
    }

    @discardableResult
    func top(_ constant: CGFloat = 0, multiplier: CGFloat = 1, relate: NSLayoutRelation = .equal, priority: UILayoutPriority = UILayoutPriorityDefaultHigh) -> Layout {
        let layout = NSLayoutConstraint(
            item: first,
            attribute: .top,
            relatedBy: relate,
            toItem: second,
            attribute: .top,
            multiplier: multiplier,
            constant: constant
        )
        layout.priority = priority
        view.addConstraint(layout)
        constrants.append(layout)
        return self
    }

    @discardableResult
    func bottom(_ constant: CGFloat = 0, multiplier: CGFloat = 1, relate: NSLayoutRelation = .equal, priority: UILayoutPriority = UILayoutPriorityDefaultHigh) -> Layout {
        let layout = NSLayoutConstraint(
            item: first,
            attribute: .bottom,
            relatedBy: relate,
            toItem: second,
            attribute: .bottom,
            multiplier: multiplier,
            constant: constant
        )
        layout.priority = priority
        view.addConstraint(layout)
        constrants.append(layout)
        return self
    }

    @discardableResult
    func centerX(_ constant: CGFloat = 0, multiplier: CGFloat = 1, relate: NSLayoutRelation = .equal, priority: UILayoutPriority = UILayoutPriorityDefaultHigh) -> Layout {
        let layout = NSLayoutConstraint(
            item: first,
            attribute: .centerX,
            relatedBy: relate,
            toItem: second,
            attribute: .centerX,
            multiplier: multiplier,
            constant: constant
        )
        layout.priority = priority
        view.addConstraint(layout)
        constrants.append(layout)
        return self
    }

    @discardableResult
    func centerY(_ constant: CGFloat = 0, multiplier: CGFloat = 1, relate: NSLayoutRelation = .equal, priority: UILayoutPriority = UILayoutPriorityDefaultHigh) -> Layout {
        let layout = NSLayoutConstraint(
            item: first,
            attribute: .centerY,
            relatedBy: relate,
            toItem: second,
            attribute: .centerY,
            multiplier: multiplier,
            constant: constant
        )
        layout.priority = priority
        view.addConstraint(layout)
        constrants.append(layout)
        return self
    }

    @discardableResult
    func width(_ constant: CGFloat = 0, multiplier: CGFloat = 1, relate: NSLayoutRelation = .equal, priority: UILayoutPriority = UILayoutPriorityDefaultHigh) -> Layout {
        let layout = NSLayoutConstraint(
            item: first,
            attribute: .width,
            relatedBy: relate,
            toItem: second,
            attribute: .width,
            multiplier: multiplier,
            constant: constant
        )
        layout.priority = priority
        view.addConstraint(layout)
        constrants.append(layout)
        return self
    }

    @discardableResult
    func height(_ constant: CGFloat = 0, multiplier: CGFloat = 1, relate: NSLayoutRelation = .equal, priority: UILayoutPriority = UILayoutPriorityDefaultHigh) -> Layout {
        let layout = NSLayoutConstraint(
            item: first,
            attribute: .height,
            relatedBy: relate,
            toItem: second,
            attribute: .height,
            multiplier: multiplier,
            constant: constant
        )
        layout.priority = priority
        view.addConstraint(layout)
        constrants.append(layout)
        return self
    }

    // MARK: Two Edge Methods

    /// width and height
    @discardableResult
    func size(_ constant: CGFloat = 0, multiplier: CGFloat = 1) -> Layout {
        let _ = {
            let layout = NSLayoutConstraint(
                item: first,
                attribute: .width,
                relatedBy: .equal,
                toItem: second,
                attribute: .width,
                multiplier: multiplier,
                constant: constant
            )
            view.addConstraint(layout)
            constrants.append(layout)
        }()
        let _ = {
            let layout = NSLayoutConstraint(
                item: first,
                attribute: .height,
                relatedBy: .equal,
                toItem: second,
                attribute: .height,
                multiplier: multiplier,
                constant: constant
            )
            view.addConstraint(layout)
            constrants.append(layout)
        }()
        return self
    }

    /// centerX and centerY
    @discardableResult
    func center(_ constant: CGFloat = 0, multiplier: CGFloat = 1) -> Layout {
        let _ = {
            let layout = NSLayoutConstraint(
                item: first,
                attribute: .centerX,
                relatedBy: .equal,
                toItem: second,
                attribute: .centerX,
                multiplier: multiplier,
                constant: constant
            )
            view.addConstraint(layout)
            constrants.append(layout)
        }()
        let _ = {
            let layout = NSLayoutConstraint(
                item: first,
                attribute: .centerY,
                relatedBy: .equal,
                toItem: second,
                attribute: .centerY,
                multiplier: multiplier,
                constant: constant
            )
            view.addConstraint(layout)
            constrants.append(layout)
        }()
        return self
    }

    // MARK: Four Edge Methods

    /// top, bottom, leading, trailing
    @discardableResult
    func edges(zoom: CGFloat = 0) -> Layout {
        let _ = {
            let layout = NSLayoutConstraint(
                item: first,
                attribute: .top,
                relatedBy: .equal,
                toItem: second,
                attribute: .top,
                multiplier: 1,
                constant: zoom
            )
            view.addConstraint(layout)
            constrants.append(layout)
        }()
        let _ = {
            let layout = NSLayoutConstraint(
                item: first,
                attribute: .bottom,
                relatedBy: .equal,
                toItem: second,
                attribute: .bottom,
                multiplier: 1,
                constant: -zoom
            )
            view.addConstraint(layout)
            constrants.append(layout)
        }()
        let _ = {
            let layout = NSLayoutConstraint(
                item: first,
                attribute: .leading,
                relatedBy: .equal,
                toItem: second,
                attribute: .leading,
                multiplier: 1,
                constant: zoom
            )
            view.addConstraint(layout)
            constrants.append(layout)
        }()
        let _ = {
            let layout = NSLayoutConstraint(
                item: first,
                attribute: .trailing,
                relatedBy: .equal,
                toItem: second,
                attribute: .trailing,
                multiplier: 1,
                constant: -zoom
            )
            view.addConstraint(layout)
            constrants.append(layout)
        }()
        return self
    }

    /// width, height, centerX, centerY
    @discardableResult
    func align(offset: CGFloat = 0) -> Layout {
        let _ = {
            let layout = NSLayoutConstraint(
                item: first,
                attribute: .width,
                relatedBy: .equal,
                toItem: second,
                attribute: .width,
                multiplier: 1,
                constant: 0
            )
            view.addConstraint(layout)
            constrants.append(layout)
        }()
        let _ = {
            let layout = NSLayoutConstraint(
                item: first,
                attribute: .height,
                relatedBy: .equal,
                toItem: second,
                attribute: .height,
                multiplier: 1,
                constant: 0
            )
            view.addConstraint(layout)
            constrants.append(layout)
        }()
        let _ = {
            let layout = NSLayoutConstraint(
                item: first,
                attribute: .centerX,
                relatedBy: .equal,
                toItem: second,
                attribute: .centerX,
                multiplier: 1,
                constant: offset
            )
            view.addConstraint(layout)
            constrants.append(layout)
        }()
        let _ = {
            let layout = NSLayoutConstraint(
                item: first,
                attribute: .centerY,
                relatedBy: .equal,
                toItem: second,
                attribute: .centerY,
                multiplier: 1,
                constant: offset
            )
            view.addConstraint(layout)
            constrants.append(layout)
        }()
        return self
    }
}

// MARK: - Advanced Using

// MAKR: Custom Operation

/// Using to set the layout's priority.
infix operator >>>: AdditionPrecedence

// MARK: Safe Call Interface

extension Layout {

    static weak var `super`: UIView?

    @discardableResult
    func auto(_ layouts: () -> Void) -> Layout {
        DispatchQueue.main.sync {
            Layout.super = self.view
            layouts()
            Layout.super = nil
        }
        return self
    }

}

// MARK: - Layout.View

extension Layout {

    class View {
        weak var `super`: UIView?
        weak var view: UIView!
        var attribute: NSLayoutAttribute

        var constant: CGFloat = 0
        var multiplier: CGFloat = 1
        var priority: UILayoutPriority?

        init(view: UIView, attribute: NSLayoutAttribute) {
            self.view = view
            self.attribute = attribute
        }
    }
}

// MARK: - Custom Operations

extension Layout.View {

    // MARK: Results

    @discardableResult
    static func == (left: Layout.View, right: Layout.View) -> NSLayoutConstraint {
        let layout = NSLayoutConstraint(item: left.view, attribute: left.attribute, relatedBy: .equal, toItem: right.view, attribute: right.attribute, multiplier: right.multiplier, constant: right.constant)
        if right.priority != nil {
            layout.priority = right.priority!
        }
        if right.super == nil {
            Layout.super?.addConstraint(layout)
        } else {
            right.super?.addConstraint(layout)
        }
        return layout
    }

    @discardableResult
    static func == (left: Layout.View, right: CGFloat) -> NSLayoutConstraint {
        let layout = NSLayoutConstraint(item: left.view, attribute: left.attribute, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: right)
        left.view.addConstraint(layout)
        return layout
    }

    @discardableResult
    static func <= (left: Layout.View, right: Layout.View) -> NSLayoutConstraint {
        let layout = NSLayoutConstraint(item: left.view, attribute: left.attribute, relatedBy: .lessThanOrEqual, toItem: right.view, attribute: right.attribute, multiplier: right.multiplier, constant: right.constant)
        if right.priority != nil {
            layout.priority = right.priority!
        }
        if right.super == nil {
            Layout.super?.addConstraint(layout)
        } else {
            right.super?.addConstraint(layout)
        }
        return layout
    }

    @discardableResult
    static func >= (left: Layout.View, right: Layout.View) -> NSLayoutConstraint {
        let layout = NSLayoutConstraint(item: left.view, attribute: left.attribute, relatedBy: .greaterThanOrEqual, toItem: right.view, attribute: right.attribute, multiplier: right.multiplier, constant: right.constant)
        if right.priority != nil {
            layout.priority = right.priority!
        }
        if right.super == nil {
            Layout.super?.addConstraint(layout)
        } else {
            right.super?.addConstraint(layout)
        }
        return layout
    }

    // MARK: Values Set

    static func + (left: Layout.View, right: CGFloat) -> Layout.View {
        left.constant = right
        return left
    }
    static func - (left: Layout.View, right: CGFloat) -> Layout.View {
        left.constant = -right
        return left
    }
    static func * (left: Layout.View, right: CGFloat) -> Layout.View {
        left.multiplier = right
        return left
    }
    static func / (left: Layout.View, right: CGFloat) -> Layout.View {
        left.multiplier = 1/right
        return left
    }
    static func >>> (left: Layout.View, right: UILayoutPriority) -> Layout.View {
        left.priority = right
        return left
    }

}

// MARK: - Extension UIView

extension UIView {

    var leading: Layout.View {
        return Layout.View(view: self, attribute: .leading)
    }
    var trailing: Layout.View {
        return Layout.View(view: self, attribute: .trailing)
    }
    var top: Layout.View {
        return Layout.View(view: self, attribute: .top)
    }
    var bottom: Layout.View {
        return Layout.View(view: self, attribute: .bottom)
    }
    var centerX: Layout.View {
        return Layout.View(view: self, attribute: .centerX)
    }
    var centerY: Layout.View {
        return Layout.View(view: self, attribute: .centerY)
    }
    var width: Layout.View {
        return Layout.View(view: self, attribute: .width)
    }
    var height: Layout.View {
        return Layout.View(view: self, attribute: .height)
    }

}

總結

這篇明顯的帶有水分……可能是因為我對 AutoLayout 的封裝還停留在非常淺層次的緣故。所謂的封裝也只是寫了一堆的方法,然後打包起來等著使用。唯一比較能說得上的學習點,就是對於鏈式編程的嘗試以及自定義操作符的學習。

希望在接下來的使用中,可以對它進行改進。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved