SwiftUI ScrollView VStack GeometryReader-Höhe wird ignoriert

Oct 10 2020

Ich möchte eine ScrollView außerhalb eines VStacks verwenden, damit mein Inhalt scrollbar ist, wenn der VStack über die Bildschirmgröße hinaus erweitert wird. Jetzt möchte ich es GeometryReaderim VStack verwenden und es verursacht Probleme, die ich nur durch Festlegen des GeometryReader-Rahmens lösen kann. Dies hilft mir nicht wirklich, da ich den Leser zum Definieren der Ansichtsgröße verwende.

Hier ist der Code ohne ScrollView und er funktioniert gut:

struct MyExampleView: View {
  var body: some View {
    VStack {
        Text("Top Label")
            .background(Color.red)
        
        GeometryReader { reader in
            Text("Custom Sized Label")
                .frame(width: reader.size.width, height: reader.size.width * 0.5)
                .background(Color.green)
        }
        
        Text("Bottom Label")
            .background(Color.blue)
    }
    .background(Color.yellow)
  }
}

Daraus ergibt sich folgendes Bild:

Das Etikett in benutzerdefinierter Größe sollte die volle Breite, aber die halbe Breite für die Höhe haben. Wenn ich nun denselben Code in eine ScrollView einbinde, geschieht Folgendes:

Nicht nur, dass alles kleiner wurde, auch die Höhe des benutzerdefinierten Größenetiketts wird irgendwie ignoriert. Wenn ich die Höhe des GeometryReader einstelle, kann ich dieses Verhalten anpassen, aber ich möchte, dass GeometryReader so groß wie sein Inhalt wird. Wie kann ich das erreichen?

Vielen Dank

Antworten

1 Asperi Oct 10 2020 at 20:02

Es versteht sich, dass dies GeometryReaderkein magisches Werkzeug ist, sondern nur den verfügbaren Speicherplatz im übergeordneten Kontext liest, aber ... ScrollViewkeinen eigenen verfügbaren Speicherplatz hat, es ist Null, da es den benötigten Speicherplatz aus internen Inhalten bestimmt ... also GeometryReaderhier verwenden Sie haben einen Zyklus - das Kind fragt die Eltern nach der Größe, aber die Eltern erwarten die Größe von den Kindern ... Der SwiftUI-Renderer löst dies irgendwie (es werden nur minimale bekannte Größen gefunden), um nicht abzustürzen.

Hier ist eine mögliche Lösung für Ihr Szenario - das geeignete Instrument hier sind die Ansichtseinstellungen. Vorbereitet und getestet mit Xcode 12 / iOS 14.

struct DemoLayout_Previews: PreviewProvider {
    static var previews: some View {
        Group {
          MyExampleView()
          ScrollView { MyExampleView() }
        }
    }
}

struct MyExampleView: View {
    @State private var height = CGFloat.zero
    var body: some View {
        VStack {
            Text("Top Label")
                .background(Color.red)
            
            Text("Custom Sized Label")
                .frame(maxWidth: .infinity)
                .background(GeometryReader {
                    // store half of current width (which is screen-wide)
                    // in preference
                    Color.clear
                        .preference(key: ViewHeightKey.self, 
                            value: $0.frame(in: .local).size.width / 2.0) }) .onPreferenceChange(ViewHeightKey.self) { // read value from preference in state self.height = $0
                }
                .frame(height: height) // apply from stored state
                .background(Color.green)
            
            Text("Bottom Label")
                .background(Color.blue)
        }
        .background(Color.yellow)
    }
}

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

Hinweis: ... und verwenden Sie GeometryReader nicht, wenn Sie sich nicht sicher sind, in welchem ​​Kontext sich Ihre Ansicht befindet.