2

Can anyone tell me why the following code for publishing an anchor preference does not work:

Based on: https://swiftwithmajid.com/2020/03/18/anchor-preferences-in-swiftui/

enum MyPreferenceKeyType: Hashable {
    case firstBounds
    case secondBounds
}

struct MyPreferenceKey: PreferenceKey {
    typealias Value = [MyPreferenceKeyType: Anchor<CGRect>]
    static var defaultValue: Value { [:] }

    static func reduce(value: inout Value, nextValue: () -> Value) {
        value.merge(nextValue()) { $1 }
    }
}


// Publish an anchor preference like this
.anchorPreference(key: MyPreferenceKey.self, value: .bounds) {
    [MyPreferenceKeyType.firstBounds: $0]
}

In the above case the transform function is never called and the preference does not appear to be sent up the view hierarchy. However the following code works and the anchor preference is propagated up the view hierarchy:

struct MyPreferenceKey: PreferenceKey {
    typealias Value = Anchor<CGRect>?
    static var defaultValue: Value = nil

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

// Publish an anchor preference like this
.anchorPreference(key: MyPreferenceKey.self, value: .bounds) {
    $0
}

Why is it that the transform closure is only called when there is nothing to transform?

Sam
  • 616
  • 7
  • 19

1 Answers1

0

Works fine. I assume the issue is in code that use this, so here is usage of your initial dictionary-based variant. Just for demo:

var body: some View {
    HStack {
        Text("Hello")
            .anchorPreference(key: MyPreferenceKey.self, value: .bounds) {
                [.firstBounds: $0]
            }
        Text("World")
    }
    .overlayPreferenceValue(MyPreferenceKey.self) { prefs in
        if let first = prefs[.firstBounds] {
            GeometryReader { gp in
                Rectangle()
                    .stroke(Color.red, lineWidth: 5)
                    .frame(width: gp[first].width, height: gp[first].height)
                    .position(x: gp[first].midX, y: gp[first].midY)
            }
        }
    }
}

demo

Tested with Xcode 13.4 / iOS 15.5

*if you add similarly for Text("World") and .secondBounds, you get for both

demo

Asperi
  • 228,894
  • 20
  • 464
  • 690
  • I see this works as you're displaying a Rectangle who's geometry updates with the preference. What doesn't work in my case is when I want to use the views geometry along with the preference to update the state of another view. Is that possible or do I need a dummy view that updates with the preference? – Sam Jul 03 '22 at 10:40
  • Anchor is not a data itself, it is not possible to use it directly as-is, it can be resolved into context of specific geometry reader as shown. If you need just pass rect somewhere it is possible to use approach like in https://stackoverflow.com/a/63223498/12299030, when rects are just observed and accumulated in state to work with then anywhere. – Asperi Jul 03 '22 at 10:44
  • I'm having difficulty getting the if condition to pass for the `prefs[.firstBounds]` like in your example Asperi. If I put a breakpoint past it, it never gets called but if I remove the if conditional it gets hit. Any ideas? – MichaelGofron Apr 13 '23 at 22:09