簡體   English   中英

layoutSubviews vs frame didSet用於檢測視圖的框架何時更改

[英]layoutSubviews vs frame didSet to detect when the view's frame changed

假設我有一個UIView子類,當其框架更改時,該子類需要更新某些內容。 例如,當它在偶數像素上時,它將是紅色,而當它在奇數像素上時將是藍色。

我已經看到至少四種方法可以做到這一點:

  1. view.layoutSubviews()

     override func layoutSubviews() { super.layoutSubviews() backgroundColor = calculateColorFromFrame(frame) } 
  2. view.frame didSet

     override var frame: CGRect { didSet { backgroundColor = calculateColorFromFrame(frame) } } 
  3. 在視圖的框架上添加一個KVO觀察器。

  4. 在視圖的包裝UIViewController上,實現viewDidLayoutSubviews()

     override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() view.updateBackgroundColorForNewFrame() } 
  5. 您知道其他方式嗎?

這些方法中的哪一個是首選,並且在本質上優於另一種? 蘋果的官方建議是什么?

  1. layoutSubviews :您可以依靠在大小已更改的視圖上調用該方法,也可以依靠該視圖接收到setNeedsLayout 如果視圖的位置發生變化,但是其大小沒有變化,則系統不會自動在該視圖上調用layoutSubviews

  2. frame didSet :適用於使用autoresizingMask布局的視圖。 它不適用於使用普通約束布局的視圖。 相反,自動布局會設置視圖的centerbounds 但是,這些是實現細節,可能會在不同版本的UIKit中發生變化。 我在Xcode 10 beta 6隨附的iOS 12.0 Simulator上進行了測試。

  3. frame KVO:出於某些原因,該功能在某些情況下與#2相同。 另外, frame沒有文檔證明是KVO兼容的,因此允許UIKit在不通知觀察者的情況下更改frame

  4. viewDidLayoutSubviews :僅當視圖是視圖控制器的view屬性時才有效,然后僅在view收到layoutSubviews消息時才有效(請參閱#1)。 同樣重要的是要理解,當調用此方法時,視圖控制器的view及其直接子視圖已經被布局,但是更深的子視圖尚未被布局。 (見這個答案的更全面的解釋。)

還要注意,視圖的frame是根據其layer某些屬性計算的:該圖層的positionbounds.sizeanchorPointtransform 如果有任何方法直接設置這些圖層屬性,則以上方法均無效。

因此,在視圖位置發生變化時,沒有特別好的通知方法。 技術上正確的方法可能是使用CAAction 當圖層檢測到其position正在改變時,它會請求其委托(對於視圖圖層來說,是視圖本身)來執行某項操作。 它通過發送actionForLayer:forKey:消息進行詢問。 因此,您可以在視圖子類中重寫action(forLayer:forKey:)以可靠地得到通知。 但是,該層更改其position (並因此更改其frame之前會先請求操作。 更改其position 后,它將運行該動作。 因此,您必須經過以下舞蹈:

override func action(for layer: CALayer, forKey event: String) -> CAAction? {
    class MyAction: CAAction {
        init(view: MyView, nextAction: CAAction?) {
            self.view = view
            self.nextAction = nextAction
        }
        let view: MyView
        let nextAction: CAAction?
        func run(forKey event: String, object: Any, arguments: [AnyHashable : Any]?) {


            // THIS IS WHERE YOU LOOK AT THE NEW FRAME AND ACT ACCORDINGLY.


            print("This is the action. Frame now: \(view.frame)")
            nextAction?.run(forKey: event, object: object, arguments: arguments)
        }
    }

    let action = super.action(for: layer, forKey: event)
    if event == "position" {
        return MyAction(view: self, nextAction: action)
    } else {
        return action
    }
}

actionForLayer:forKey:UIView實現通常返回nil 但是在動畫塊內(包括當界面在縱向和橫向之間旋轉時),它返回(對於某些鍵)一個將CAAnimation安裝在圖層上的動作。 因此,如果有的話,運行super返回的動作很重要。

我可能對您的問題理解不正確或對Apple文檔的理解有誤,但以下是我對使用什么來查看鏡架變化的看法:

  1. 您不應直接調用view.layoutSubviews()。 只能由UIView的子類覆蓋LayoutSubviews方法“僅當子視圖的自動調整大小和基於約束的行為無法提供所需的行為時,才可以使用實現直接設置子視圖的框架矩形”。 -從Apple文檔中復制。 因此,我不會使用此方法,因為它不適用於您要更改顏色的視圖,但不適用於您的視圖的子視圖。
  2. 我認為這是一種非常優雅的使用方式,因為如果移動視圖,則框架將設置為新值。 但是,當框架更改大小時,didSet也將被調用,並且框架可能不會移動。 如果您的calculateColorFromFrame(frame)是一種昂貴的方法,並且視圖可以更改大小,則可能會導致性能下降。
  3. 我認為這是與第二點相同的繁瑣方法。 唯一的事情是,您現在可以使用另一個類來觀察視圖框架中的更改,並調用calculateColorFromFrame(frame)方法。
  4. 各個視圖控制器中的viewDidLayoutSubviews()也是一種好方法,但是,當該視圖控制器的主視圖中的某些內容發生更改並且該視圖必須重新布置其子視圖時,此處將調用此方法。 這也意味着當您的視圖沒有移動或更改並且calculateColorFromFrame(frame)不變時,可以調用此方法。 因此,這可能經常會多次調用此方法。

我很好奇的是,您如何有效地計算奇數和偶數像素,因為幀位置和大小基於點,並且一個點可以跨越一個以上的像素。

我希望這能回答您的問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM