Why isn't onPreferenceChange being called if it's inside a ScrollView in SwiftUI?

前端 未结 5 2037
逝去的感伤
逝去的感伤 2021-01-02 02:57

I\'ve been seeing some strange behavior for preference keys with ScrollView. If I put the onPreferenceChange inside the ScrollView it won\'t be cal

5条回答
  •  囚心锁ツ
    2021-01-02 03:07

    I think onPreferenceChange in your example is not called because it’s function is profoundly different from preference(key…)

    preference(key:..) sets a preference value for the view it is used on. whereas onPreferenceChange is a function called on a parent view – a view on a higher position in the view tree hierarchy. Its function is to go through all its children and sub-children and collect their preference(key:) values. When it found one it will use the reduce function from the PreferenceKey on this new value and all the already collected values. Once it has all the values collected and reduced them it will execute the onPreference closure on the result.

    In your first example this closure is never called because the Text(“Hello”) view has no children which set the preference key value (in fact the view has no children at all). In your second example the Scroll view has a child which sets its preference value (the Text view).

    All this does not explain the multiple times per frame error – which is most likely unrelated.

    Recent update (24.4.2020): In a similar case I could induce the call of onPreferenceChange by changing the Equatable condition for the PreferenceData. PreferenceData needs to be Equatable (probably to detect a change in them). However, the Anchor type by itself is not equatable any longer. To extract the values enclosed in an Anchor type a GeometryProxy is required. You get a GeometryProxy via a GeometryReader. For not disturbing the design of views by enclosing some of them into a GeometryReader I generated one in the equatable function of the PreferenceData struct:

    struct ParagraphSizeData: Equatable {
      let paragraphRect: Anchor?
    
      static func == (value1: ParagraphSizeData, value2: ParagraphSizeData) -> Bool {
    
        var theResult : Bool = false
        let _ = GeometryReader { geometry in
           generateView(geometry:geometry, equality:&theResult)
        }
    
        func generateView(geometry: GeometryProxy, equality: inout Bool) -> Rectangle {
           let paragraphSize1, paragraphSize2: NSSize
    
          if let anAnchor = value1.paragraphRect { paragraphSize1 = geometry[anAnchor].size }
           else {paragraphSize1 = NSZeroSize }
           if let anAnchor = value2.paragraphRect { paragraphSize2 = geometry[anAnchor].size }
           else {paragraphSize2 = NSZeroSize }
    
           equality = (paragraphSize1 == paragraphSize2)
           return Rectangle()
        }
    
       return theResult     
      }
    
    }
    

    With kind regards

提交回复
热议问题