SwiftUI: PreferenceKey Reduce method never gets called

Viewed 1675

I am trying to pass data up a view chain using Preferences but .onPreferenceChange is never called. Investigating a bit reveals that while the Debugger shows execution passes through my .Preference modifier, the reduce method is never called to push the new value into the key. I can not figure out why, nor can I find a way to examine the View's Preference key:value data.

here is the code:

struct LittleTapPrefKey: PreferenceKey {
    typealias Value = String
    static var defaultValue: Value = "A0"
    static func reduce(value: inout Value, nextValue: () -> String) {
        print("Inside Little Reduce \(value)and NextVal =  \(nextValue())")
        value = nextValue()
    }
}

here is cell View where on tap of a GlyphCell instance in a QGrid, the Pref modifier is set:

struct GlyphCell: View {

    var glyph:Glyph
    @State private var selected = false   // << track own selection

    var body: some View {
        ZStack {
            Text("\(glyph.Hieroglyph)")
                .lineLimit(1)
                .padding(.all, 12.0)
                .font(.system(size: 40.0))
                .background(Color.yellow)
            Text("\(glyph.Gardiner)")
                .offset(x: 12, y: 28)
                .foregroundColor(.blue)
        }.onTapGesture {
            print("Tap on \(self.glyph.Gardiner)")
            self.selected.toggle()
        }
        .cornerRadius(6.0)
        .frame(width: 90.0, height: 100.0, alignment: .center)
        .previewLayout(.sizeThatFits)
        .background(Group {
            if self.selected {
                Color.clear
                    .preference(key: LittleTapPrefKey.self, value: self.glyph.Gardiner)
            } // end selected test
        }   // end  group
      )     // end .background
    }       // end cell body view
}

with a Breakpoint set on the .pref(key:...), the breakpoint triggers every time a cell is tapped, and the value of self.glyph.Gardiner is correct at that point.

Breakpoints or the test Print statement in the Preference Key Struct "reduce" function are never triggered. Since the .onPrefChange is never triggered, I have to assume the Value never gets changed, but I have no way to directly view the Pref value.

What is going on?

1 Answers

The reduce is called when you set one preference key in one view hierarchy several times, so SwiftUI gives you possibility to combine them somehow, eg:

.previewLayout(.sizeThatFits)
.preference(key: LittleTapPrefKey.self, value: value1) // 1st time
.background(Group {
    if self.selected {
        Color.clear
            .preference(key: LittleTapPrefKey.self, value: value2) // 2nd time
    }
}

in your case you have only one value setter, so it is just set and .onPreferenceChange(LittleTapPrefKey.self) callback modifier works as expected.

Tested with Xcode 11.4 / iOS 13.4

Related