3

Use this code as a reference.

If you comment out the code and leave the contents empty, and then run it, the same behavior will occur.

struct SizePreferenceKey: PreferenceKey {
    typealias Value = CGSize
    static var defaultValue: Value = .zero

    static func reduce(value _: inout Value, nextValue: () -> Value) {
//        _ = nextValue() <-- comment out
    }
}

I tried the same thing to this code, and it works fine.

struct ViewOffsetKey: PreferenceKey {
    typealias Value = CGFloat
    static var defaultValue = CGFloat.zero
    static func reduce(value: inout Value, nextValue: () -> Value) {
//        value += nextValue() <-- comment out
    }
}

I have to write it to conform the protocol, but it seems to work even if the content is empty. Why is this? When do I need the content?

In other words, it seems that onPreferenceChange is working fine without updating the value. Why is this?

shingo.nakanishi
  • 2,045
  • 2
  • 21
  • 55
  • `reduce` is only relevant when you have multiple views using the same key. That does not happen in neither of the two examples you referenced. The answers might be used in a situation where that does happen, which is probably the answers included the implementation for `reduce`. – Sweeper Aug 09 '21 at 07:52

1 Answers1

3

The reduce is needed to process preference values for sibling views (ie. same preference key is used by views in one same level of container, so it is called one after another and it is needed to decide what to do - reduce gives us a chance for that).

Here is an example (based on your preference key) of how to calculate common size of sibling views. If you remove content of reduce only first call preference value will be used (by design)

Demo is prepared with Xcode 13 / iOS 15

demo

extension CGSize {
    static func +=(lhs: inout CGSize, rhs: CGSize) {
        lhs.width += rhs.width
        lhs.height += rhs.height
    }
}

struct SizePreferenceKey: PreferenceKey {
    typealias Value = CGSize
    static var defaultValue: Value = .zero

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

struct TestPrefReduce: View {
    @State private var size: CGSize = .zero

    var body: some View {
        VStack {
            Text("Common size: [\(Int(size.width)) x \(Int(size.height))]")
            Divider()
            Text("Title").font(.title)
                .background(GeometryReader {
                    Color.clear.preference(key: SizePreferenceKey.self,
                        value: $0.frame(in: .local).size)
                     })
            Text("Hello, World!").font(.headline)
                .background(GeometryReader {
                    Color.clear.preference(key: SizePreferenceKey.self,
                        value: $0.frame(in: .local).size)
                     })
        }
        .onPreferenceChange(SizePreferenceKey.self) {
              self.size = $0
        }
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • What's the difference between `$0.size` and `$0.frame(in: .local).size`? I would've thought that was the same – George Aug 09 '21 at 09:44