6

I have one parent View which has two Child views.

When the child view changes the preference value, the parent receives the preference changes via onPreferenceChange However, when the parent has a Date Picker, onPreferenceChange doesn't get called and stops working. Has anyone found any workaround for this ?

struct parentView: View {

@State private var date = Date()

var body: some View {
    
    VStack{
        
        ChildView1()
        
        Text("Child View 2")
    
       // DatePicker("", selection: self.$date, displayedComponents: .hourAndMinute)
       

    }
    .onPreferenceChange(testPreference.self, perform: { value in
        print("Printing preference value")
        print(value)
    })
 }
}


struct ChildView1: View {

@State private var tablets : Int = 0

var body: some View {
    Text("Child View 1")
        .onTapGesture {
            self.tablets = self.tablets + 1
        }
        .preference(key: testPreference.self, value: self.tablets)

  }
}


struct testPreference: PreferenceKey {

typealias Value = Int

static var defaultValue: Int = 0

static func reduce(value: inout Int, nextValue: () -> Int) {
    value = nextValue()
  }
}
2
  • 1
    This has to be a SwiftUI bug (reproduced in 12.0.1). I recommend opening a feedback on it. The only fix I've found so far is to move the .onPreferenceChange from the VStack to ChildView1(). Even putting it on the Text bizarrely doesn't work. Burying the DatePicker in a sub-container also doesn't work. It feels like it's pointing to some kind of hack inside of DatePicker. Commented Sep 30, 2020 at 21:04
  • Thank you so much. Your workaround seems to work. Commented Sep 30, 2020 at 21:29

1 Answer 1

18

I also got stuck on the same problem and Apple told me this was expected. Our fault is we implemented this reduce() incorrectly:

static func reduce(value: inout Int, nextValue: () -> Int) {
    value = nextValue()
}

This overwrites values explicitly written by views, by the default value written by every view. Therefore, you need to rewrite that function so that applying it to the default value has no effect, as described in the documentation of PreferenceKey.defaultValue:

Views that have no explicit value for the key produce this default value. Combining child views may remove an implicit value produced by using the default. This means that reduce(value: &x, nextValue: {defaultValue}) shouldn’t change the meaning of x.

So you'd need to replace this line:

value = nextValue()

with this:

value = max(value, nextValue())

or this:

value += nextValue()

or something like that.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.