snapkit equalto和multipliedby方法

来源:https://www.cnblogs.com/shenyuiOS/archive/2018/09/26/9706832.html
-Advertisement-
Play Games

最近在使用snapkit過程中遇到一個問題,在github上搜索之後發現另外一個有趣的問題 看起來很理所當然的,明顯不可以這樣寫,但是具體是什麼原因呢,明明沒有報任何錯誤和警告,但是.multipliedBy()方法卻沒有效果,那我們來看一下snapkit源碼。 1.首先點進equalTo()方法, ...


最近在使用snapkit過程中遇到一個問題,在github上搜索之後發現另外一個有趣的問題

frameImageContainer.snp.makeConstraints({ (make) in 
    make.width.equalTo(295).multipliedBy(0.2) 
    make.height.equalTo(355).multipliedBy(0.2) 
    make.top.equalToSuperview().offset(self.view.frame.height/8) 
    make.centerX.equalToSuperview(); 
})

看起來很理所當然的,明顯不可以這樣寫,但是具體是什麼原因呢,明明沒有報任何錯誤和警告,但是.multipliedBy()方法卻沒有效果,那我們來看一下snapkit源碼。

1.首先點進equalTo()方法,代碼是這樣的:

@discardableResult
    public func equalTo(_ other: ConstraintRelatableTarget, _ file: String = #file, _ line: UInt = #line) -> ConstraintMakerEditable {
        return self.relatedTo(other, relation: .equal, file: file, line: line)
    }

再點進relatedTo()方法:

internal func relatedTo(_ other: ConstraintRelatableTarget, relation: ConstraintRelation, file: String, line: UInt) -> ConstraintMakerEditable {
        let related: ConstraintItem
        let constant: ConstraintConstantTarget
        
        if let other = other as? ConstraintItem {
            guard other.attributes == ConstraintAttributes.none ||
                  other.attributes.layoutAttributes.count <= 1 ||
                  other.attributes.layoutAttributes == self.description.attributes.layoutAttributes ||
                  other.attributes == .edges && self.description.attributes == .margins ||
                  other.attributes == .margins && self.description.attributes == .edges else {
                fatalError("Cannot constraint to multiple non identical attributes. (\(file), \(line))");
            }
            
            related = other
            constant = 0.0
        } else if let other = other as? ConstraintView {
            related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
            constant = 0.0
        } else if let other = other as? ConstraintConstantTarget {
            related = ConstraintItem(target: nil, attributes: ConstraintAttributes.none)
            constant = other
        } else if #available(iOS 9.0, OSX 10.11, *), let other = other as? ConstraintLayoutGuide {
            related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
            constant = 0.0
        } else {
            fatalError("Invalid constraint. (\(file), \(line))")
        }
        
        let editable = ConstraintMakerEditable(self.description)
        editable.description.sourceLocation = (file, line)
        editable.description.relation = relation
        editable.description.related = related
        editable.description.constant = constant
        return editable
    }

可以看到上面紅色部分,此時other可以轉換為ConstraintConstantTarget類型,設置related的target為nil,attributes為none,constant設置為other,最後將這些變數賦值給description屬性保存。

2.multipliedBy()方法:

@discardableResult
    public func multipliedBy(_ amount: ConstraintMultiplierTarget) -> ConstraintMakerEditable {
        self.description.multiplier = amount
        return self
    }

可以看到,multipliedBy方法中只是簡單的賦值,將amout值複製到description中保存。

3.再來看一下makeConstraints()方法:

internal static func makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
        let maker = ConstraintMaker(item: item)
        closure(maker)
        var constraints: [Constraint] = []
        for description in maker.descriptions {
            guard let constraint = description.constraint else {
                continue
            }
            constraints.append(constraint)
        }
        for constraint in constraints {
            constraint.activateIfNeeded(updatingExisting: false)
        }
    }

首先將要添加約束的對象封裝成ConstraintMaker對象,再把它作為參數調用closure,開始添加約束。closure中的每條語句會先被解析ConstraintDescription對象,添加到maker的descriptions屬性中。 在第一個for迴圈中可以看到,每次迴圈從descriptions中取出一條description,判斷是否改description是否有constraint屬性;

constraint屬性使用的懶載入,值為:

internal lazy var constraint: Constraint? = {
        guard let relation = self.relation,
              let related = self.related,
              let sourceLocation = self.sourceLocation else {
            return nil
        }
        let from = ConstraintItem(target: self.item, attributes: self.attributes)
        
        return Constraint(
            from: from,
            to: related,
            relation: relation,
            sourceLocation: sourceLocation,
            label: self.label,
            multiplier: self.multiplier,
            constant: self.constant,
            priority: self.priority
        )
    }()

先判斷relation,related,sourceLocation的值是否為nil,若為nil則返回nil,否則就根據description屬性創建一個Constraint對象並返回。

Constraint的構造方法:

    internal init(from: ConstraintItem,
                  to: ConstraintItem,
                  relation: ConstraintRelation,
                  sourceLocation: (String, UInt),
                  label: String?,
                  multiplier: ConstraintMultiplierTarget,
                  constant: ConstraintConstantTarget,
                  priority: ConstraintPriorityTarget) {
        self.from = from
        self.to = to
        self.relation = relation
        self.sourceLocation = sourceLocation
        self.label = label
        self.multiplier = multiplier
        self.constant = constant
        self.priority = priority
        self.layoutConstraints = []

        // get attributes
        let layoutFromAttributes = self.from.attributes.layoutAttributes
        let layoutToAttributes = self.to.attributes.layoutAttributes

        // get layout from
        let layoutFrom = self.from.layoutConstraintItem!

        // get relation
        let layoutRelation = self.relation.layoutRelation

        for layoutFromAttribute in layoutFromAttributes {
            // get layout to attribute
            let layoutToAttribute: LayoutAttribute
            #if os(iOS) || os(tvOS)
                if layoutToAttributes.count > 0 {
                    if self.from.attributes == .edges && self.to.attributes == .margins {
                        switch layoutFromAttribute {
                        case .left:
                            layoutToAttribute = .leftMargin
                        case .right:
                            layoutToAttribute = .rightMargin
                        case .top:
                            layoutToAttribute = .topMargin
                        case .bottom:
                            layoutToAttribute = .bottomMargin
                        default:
                            fatalError()
                        }
                    } else if self.from.attributes == .margins && self.to.attributes == .edges {
                        switch layoutFromAttribute {
                        case .leftMargin:
                            layoutToAttribute = .left
                        case .rightMargin:
                            layoutToAttribute = .right
                        case .topMargin:
                            layoutToAttribute = .top
                        case .bottomMargin:
                            layoutToAttribute = .bottom
                        default:
                            fatalError()
                        }
                    } else if self.from.attributes == self.to.attributes {
                        layoutToAttribute = layoutFromAttribute
                    } else {
                        layoutToAttribute = layoutToAttributes[0]
                    }
                } else {
                    if self.to.target == nil && (layoutFromAttribute == .centerX || layoutFromAttribute == .centerY) {
                        layoutToAttribute = layoutFromAttribute == .centerX ? .left : .top
                    } else {
                        layoutToAttribute = layoutFromAttribute
                    }
                }
            #else
                if self.from.attributes == self.to.attributes {
                    layoutToAttribute = layoutFromAttribute
                } else if layoutToAttributes.count > 0 {
                    layoutToAttribute = layoutToAttributes[0]
                } else {
                    layoutToAttribute = layoutFromAttribute
                }
            #endif

            // get layout constant
            let layoutConstant: CGFloat = self.constant.constraintConstantTargetValueFor(layoutAttribute: layoutToAttribute)

            // get layout to
            var layoutTo: AnyObject? = self.to.target

            // use superview if possible
            if layoutTo == nil && layoutToAttribute != .width && layoutToAttribute != .height {
                layoutTo = layoutFrom.superview
            }

            // create layout constraint
            let layoutConstraint = LayoutConstraint(
                item: layoutFrom,
                attribute: layoutFromAttribute,
                relatedBy: layoutRelation,
                toItem: layoutTo,
                attribute: layoutToAttribute,
                multiplier: self.multiplier.constraintMultiplierTargetValue,
                constant: layoutConstant
            )

            // set label
            layoutConstraint.label = self.label

            // set priority
            layoutConstraint.priority = LayoutPriority(rawValue: self.priority.constraintPriorityTargetValue)

            // set constraint
            layoutConstraint.constraint = self

            // append
            self.layoutConstraints.append(layoutConstraint)
        }
    }

重點看紅色部分,遍歷layoutAttributes,並根據layoutAttribute的值生成一個LayoutConstraint對象添加到layoutConstraints數組中。LayoutConstraint繼承自系統類NSLayoutConstraint。

4.最後再看3中的第二個for迴圈,使用activeIfNeeded()方法激活約束:

 internal func activateIfNeeded(updatingExisting: Bool = false) {
        guard let item = self.from.layoutConstraintItem else {
            print("WARNING: SnapKit failed to get from item from constraint. Activate will be a no-op.")
            return
        }
        let layoutConstraints = self.layoutConstraints

        if updatingExisting {
            var existingLayoutConstraints: [LayoutConstraint] = []
            for constraint in item.constraints {
                existingLayoutConstraints += constraint.layoutConstraints
            }

            for layoutConstraint in layoutConstraints {
                let existingLayoutConstraint = existingLayoutConstraints.first { $0 == layoutConstraint }
                guard let updateLayoutConstraint = existingLayoutConstraint else {
                    fatalError("Updated constraint could not find existing matching constraint to update: \(layoutConstraint)")
                }

                let updateLayoutAttribute = (updateLayoutConstraint.secondAttribute == .notAnAttribute) ? updateLayoutConstraint.firstAttribute : updateLayoutConstraint.secondAttribute
                updateLayoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: updateLayoutAttribute)
            }
        } else {
            NSLayoutConstraint.activate(layoutConstraints)
            item.add(constraints: [self])
        }
    }

當updatingExisting為false時,進入else語句,使用的系統類NSLayoutConstraint的方法激活約束:

/* Convenience method that activates each constraint in the contained array, in the same manner as setting active=YES. This is often more efficient than activating each constraint individually. */
    @available(iOS 8.0, *)
    open class func activate(_ constraints: [NSLayoutConstraint])

並將設置過的約束添加到item的constraintSet這個私有屬性中:

internal var constraints: [Constraint] {
        return self.constraintsSet.allObjects as! [Constraint]
    }
    
    internal func add(constraints: [Constraint]) {
        let constraintsSet = self.constraintsSet
        for constraint in constraints {
            constraintsSet.add(constraint)
        }
    }
    
    internal func remove(constraints: [Constraint]) {
        let constraintsSet = self.constraintsSet
        for constraint in constraints {
            constraintsSet.remove(constraint)
        }
    }
    
    private var constraintsSet: NSMutableSet {
        let constraintsSet: NSMutableSet
        
        if let existing = objc_getAssociatedObject(self, &constraintsKey) as? NSMutableSet {
            constraintsSet = existing
        } else {
            constraintsSet = NSMutableSet()
            objc_setAssociatedObject(self, &constraintsKey, constraintsSet, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
        return constraintsSet
        
    }

5.通過這個過程不難發現,使用make.width.equalTo(295).multipliedBy(0.2) 這種方式不能得到想要的結果。在3中Constraint的構造方法的紅色部分,其實構造LayoutConstraint對象時調用的NSLayoutConstraint的便利構造器方法:

    /* Create constraints explicitly.  Constraints are of the form "view1.attr1 = view2.attr2 * multiplier + constant" 
     If your equation does not have a second view and attribute, use nil and NSLayoutAttributeNotAnAttribute.
     */
    public convenience init(item view1: Any, attribute attr1: NSLayoutConstraint.Attribute, relatedBy relation: NSLayoutConstraint.Relation, toItem view2: Any?, attribute attr2: NSLayoutConstraint.Attribute, multiplier: CGFloat, constant c: CGFloat)

註意上面註釋 view1.attr1 = view2.attr2 * multiplier + constant 如果只設置為數字,則相當於view2為nil,所以view1的屬性值只能等於constant的值,不會乘以multiplier。

6.終於寫完了,哈哈。  demo工程地址,對應的方法已打斷點,可以跟著代碼一步步調試,有助於理解。

 

 

疑問:在4中最後一部分紅色字體的內容,私有屬性constraintsSet為啥不直接使用,還要使用runtime給當前對象綁定一個同名的屬性,每次使用時獲取綁定的屬性的值,不懂,希望知道的同學不吝賜教。

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • mysql 正常運行的時候,[查看 table 的結構][show create table doc]並不是困難的事。 但是有時 mysql 發生故障,這種方法便不再可行。 當遇到故障,通常使用新的 mysql 實例來恢復當前的數據。 建表是非常重要的步驟,我們必須有其它的方法來尋找 table 的 ...
  • 在使用lepus3.7監控MySQL資料庫的時候,碰到了以下幾個問題,本博客給出了這些問題產生的原因,以及相應的解決辦法。 1. 問題1:php頁面無法連接資料庫 直接使用php程式執行php文件,可以連接mysql,但是在httpd中同樣的php頁面無法連接mysql。 lepus的web程式(P ...
  • 之前一直以為Linux和Windows差不多,但是學習了Linux基礎入門之後才發現兩種操作系統之間差距非常大。 Linux只是在硬體之上的內核和系統調用,就連我們在Windows里習以為常的圖形界面都是Linux上的軟體。在使用Linux的時候,我們都習慣於使用終端和命令行進行操作,而不是像Win ...
  • 提醒對話框手機上的App極大地方便了人們的生活,很多業務只需用戶拇指一點即可輕鬆辦理,然而這也帶來了一定的風險,因為有時候用戶並非真的想這麼做,只是不小心點了一下而已,如果App不做任何提示的話,繼續吭哧吭哧兀自辦完業務,比如轉錯錢了、誤刪資料了,往往令用戶追悔莫及。所以對於部分關鍵業務,App為了 ...
  • 此篇文章可以利用碎片化時間進行消化和瞭解,針對Android各個版本特性,並沒有把所有列出,只是抽出了比較常用重要的特性作為提示,同時在面試中只要牢記重要的幾個點即可,其他特性直接查找官方文檔即可。 Android5.0(棒棒糖) 1)運行時機制,採用ART.安裝時轉換為機器語言,成為真正本地應用 ...
  • 屏幕解析度:在x y軸上的像素點數。單位是px,1px=1個像素點。一般以 縱向像素×橫向像素 表示,如1920*1080dpi 每英寸上的像素點數(斜角的px數目 ÷ 斜角的inch數目),dot per inch的縮寫,與屏幕尺寸和屏幕解析度有關。 以三星C9 pro為例,官方參數為1920×1 ...
  • 最近在使用snapkit佈局時,竟然發現更新約束會導致崩潰,為什麼這樣呢? 看起來完全沒有問題,但是一運行就崩潰,崩潰位置在activeIfNeeded方法中: fatalerror信息提示找不到因存在的constraint來更新,輸出existingLayoutConstraints不為nil,輸 ...
  • 以下命令需要root許可權: svc命令 這個腳本在/system/bin目錄下,這個命令可以用來控制電源管理,wifi開關,數據開關(就是上網流量) svc power stayon [true|false|usb|ac] 這個是控制usb插入的時候屏幕是否常亮,這個有地方設置,就不多說了 svc ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...