Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI view preferences: overlayPreferenceValue returns nil when used on complex containers

Tags:

ios

swift

swiftui

I'm trying to use anchorPreference to draw an overlay over some view in my VStack. However, it only works in the simplest cases. When content of my VStack gets a little bit more complex, the overlay is never drawn.

Here's my code

struct debug_test: View {
@State private var someState = false

var body: some View {
    VStack {
        Text("Hello World !!!")
            .anchorPreference(
                key: BoundsPreferenceKey.self,
                value: .bounds
            ) { $0 }

        //////////////////////////////////////////////////////////
        // When I remove below lines - it works ok.
        // But when I put add some conditionally-visible view and one more view
        // it stops drawing an overlay.
        // Apparently, `preferences` is always nil in that case.
        if someState {
            Text("Pooop")
        }
        Text("Pooop2")
        //////////////////////////////////////////////////////////
    }
    .overlayPreferenceValue(BoundsPreferenceKey.self) { preferences in
        GeometryReader { geometry in
            preferences.map {
                Rectangle()
                    .stroke(Color.red, lineWidth: 5)
                    .frame(
                        width: geometry[$0].width,
                        height: geometry[$0].height
                )
                    .offset(
                        x: geometry[$0].minX,
                        y: geometry[$0].minY
                )
            }
        }
    }
  }
 }

As I explained in the code comments, when I get a simple stack with a single view inside, it works fine. But when I add a few more views and some conditionals inside, it stops working. Any clue how to fix it?

like image 937
msmialko Avatar asked Sep 10 '25 16:09

msmialko


1 Answers

Preferences are updated during layout and I assume your preference key held Anchor<CGRect>? which was reset to nil at the end of layout.

Here is fixed variant. Tested with Xcode 11.4 / iOS 13.4.

Note: I would recommend to use explicit struct model for your custom preference key. Consider this solution for example

demo

struct BoundsPreferenceKey: PreferenceKey {
    static var defaultValue: [Anchor<CGRect>] = [] // << use something persistent

    static func reduce(value: inout [Anchor<CGRect>], nextValue: () -> [Anchor<CGRect>]) {
        value.append(contentsOf:nextValue())
    }
}

struct debug_test: View {
@State private var someState = false

var body: some View {
    VStack {
        Text("Hello World !!!")
            .anchorPreference(
                key: BoundsPreferenceKey.self,
                value: .bounds
            ) { [$0] }

        if someState {
            Text("Pooop")
        }
        Text("Pooop2")
        //////////////////////////////////////////////////////////
    }
    .overlayPreferenceValue(BoundsPreferenceKey.self) { preferences in
        GeometryReader { geometry in
            preferences.map {
                Rectangle()
                    .stroke(Color.red, lineWidth: 5)
                    .frame(
                        width: geometry[$0].width,
                        height: geometry[$0].height
                )
                    .position(    // << here is fix as well !!
                        x: geometry[$0].midX,
                        y: geometry[$0].midY
                )
            }[0]     // << just for demo, this should be more safe !!
        }
    }
  }
 }
like image 76
Asperi Avatar answered Sep 13 '25 04:09

Asperi