SwiftUI ScrollView VStack GeometryReader Hauteur ignorée

Oct 10 2020

Je souhaite utiliser un ScrollView en dehors d'un VStack, afin que mon contenu puisse faire défiler si le VStack se développe au-delà de la taille de l'écran. Maintenant, je veux utiliser GeometryReaderdans le VStack et cela pose des problèmes, que je ne peux résoudre qu'en définissant le cadre GeometryReader, ce qui ne m'aide pas vraiment étant donné que j'utilise le lecteur pour définir la taille de la vue.

Voici le code sans ScrollView et cela fonctionne bien:

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)
  }
}

Cela donne l'image suivante:

L'étiquette de taille personnalisée doit être pleine largeur, mais la moitié de la largeur pour la hauteur. Maintenant, si j'emballe le même code dans un ScrollView, cela se produit:

Non seulement tout est devenu plus petit, mais la hauteur de l'étiquette de taille personnalisée est en quelque sorte ignorée. Si je règle la hauteur du GeometryReader, je peux ajuster ce comportement, mais je veux que GeometryReader devienne aussi grand que son contenu. Comment puis-je atteindre cet objectif?

Merci

Réponses

1 Asperi Oct 10 2020 at 20:02

Il faut comprendre que ce GeometryReadern'est pas un outil magique, il lit juste l'espace disponible dans le contexte parent actuel, mais ... ScrollViewn'a pas son propre espace disponible, il est nul, car il détermine l'espace nécessaire à partir du contenu interne ... donc en utilisant GeometryReaderici vous avez un cycle - l'enfant demande la taille au parent, mais le parent attend la taille de l'enfant ... Le moteur de rendu SwiftUI résout en quelque sorte ce problème (trouver des tailles minimales connues), juste pour ne pas planter.

Voici une solution possible pour votre scénario - l'instrument approprié ici est les préférences d'affichage. Préparé et testé avec 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()
    }
}

Remarque: ... et n'utilisez pas GeometryReader si vous n'êtes pas sûr du contexte dans lequel se trouve votre vue.