TextField appears to break preferences

I've written a view modifier that relies on the preference system to wrap a view in a ScrollView if it overflows the space it's allowed onscreen

struct ScrollableOverflow: ViewModifier {

    // MARK: State Properties

    /// Indicates if the content takes more vertical space than is available
    @State private var isOverflowing = false
    /// The vertical height of the content in pixels
    @State private var childHeight: CGFloat? = nil

    // MARK: Instance Properties

    /**
     The position of the content on the horizontal axis
 
     Since `GeometryReader` takes up all the space it's offered and always left-aligns its conent, this is necessary to apply proper horizontal positioning to the rendered content
    */
    let alignment: HorizontalAlignment

    // MARK: Initializers

    /**
     Creates a new `ScrollableOverflow` modifier
 
     - Parameter alignment: The position of the content on the horizontal axis
    */
    init(alignment: HorizontalAlignment = .center) {
        self.alignment = alignment
    }

    // MARK: ViewModifier Conformance

    func body(content: Content) -> some View {
        GeometryReader { availableGeometry in
            HStack {
                if alignment == .center || alignment == .trailing {
                    Spacer()
                }
                content
                    .fixedSize(horizontal: false, vertical: true)
                    .background(
                        GeometryReader { contentGeometry in
                            Color.clear
                                .preference(key: ScrollableChildViewHeight.self, value: dump(contentGeometry.size.height))
                        }
                    )
                    .onPreferenceChange(ScrollableChildViewHeight.self) { newChildHeight in
                        print("Modifier height: \(newChildHeight)")
                        childHeight = newChildHeight
                        isOverflowing = newChildHeight > availableGeometry.size.height
                    }
                if alignment == .center || alignment == .trailing {
                    Spacer()
                }
            }
            .scroll(when: isOverflowing)
        }
        .frame(maxHeight: childHeight)
    }

    // MARK: Helper Types

    /// The preference key used to communicate the rendered height of the child view of a `ScrollableOverflow` view modifier back up to its parent to know if a `ScrollView` needs to be rendered
    private struct ScrollableChildViewHeight: PreferenceKey {
        static let defaultValue: CGFloat = 0
        static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
            value = nextValue()
        }
    }
}

// MARK: - View Extension

extension View {

    /**
     Renders content in a scrollable way only if it overflows the vertical space it's allowed
 
     - Parameter alignment: The position of the content on the horizontal axis
    */
    func scrollOnOverflow(alignment: HorizontalAlignment = .center) -> some View {
        modifier(ScrollableOverflow(alignment: alignment))
    }

    /**
     Makes a view scrollable only when a condition applies
 
     - Parameter condition: Determines if the view should be scrollable
    */
    @ViewBuilder fileprivate func scroll(when condition: Bool) -> some View {
        if condition {
            ScrollView {
                self
            }
        }
        else {
            self
        }
    }
}

This works for most views, but when I add a TextField to the hierarchy that overflows, the outer GeometryReader of the modifier ends up always getting its height set to 0

let designedLabel = Text("Email Address or Phone Number")
    .font(.headline)

VStack(alignment: .leading, spacing: 20) {
    Text("Please fill in whichever is connected to your account to receive a verification code")
        .fixedSize(horizontal: false, vertical: true)
                
    VStack(alignment: .leading) {
        #if !os(macOS) // Platforms like macOS display the label given to the TextField directly, but not iOS
            designedLabel
                .fixedSize(horizontal: false, vertical: true)
        #endif
        TextField(text: $text, prompt: Text("you@example.com / +1 (555)-867-5309")) {
            designedLabel
        }
    }
}
.padding()
.background(Color.gray)
.cornerRadius(20)
.scrollOnOverflow()

This seems to be because the ScrollableChildViewHeight preference always takes its defaultValue (which is 0, but you can verify this by changing it to whatever you'd like), and never gets updated with the correct value. However, if you look at the console with the dump and print statements that are there, you'll see that the preference does properly get set with what seems to be a correct value, though the call to onPreferenceChange never gets anything other than the defaultValue. Removing the TextField makes this system work seemingly flawlessly

Am I missing something or misunderstanding how the preference system works, or is this a SwiftUI bug? I'm using the latest Xcode version 13.2.1 (13C100) on the latest macOS Monterey 12.2 and targeting iOS 15.2, and this seems to be the case on all the simulators and my physical iPhone 12 Pro Max

ScrollView alone does this on its own.

How do I make it behave like that? When I wrap something in a ScrollView, it always seems to be scrollable. I want the content to stay static if it can fit, but scroll if it doesn't. Also, ScrollView always takes all the size it's given, but I want it to take only the size of its child in the event that its child is smaller than the allowed space

TextField doesn't't know the height it needs unless you tell.

Even if that's the case, I would expect that would mean the height of the VStack (and its enclosing GeometryReader) would then be equal to the height of the Text (plus spacing), but that's not what's happening. Instead, VStack's height is correctly reported if you look at the result of the calls to dump (including the size of the TextField), but including the TextField just breaks reporting to the preference system

TextField appears to break preferences
 
 
Q