Altura do SwiftUI ScrollView VStack GeometryReader ignorada

Oct 10 2020

Quero usar um ScrollView fora de um VStack, para que meu conteúdo seja rolável se o VStack se expandir além do tamanho da tela. Agora quero usar GeometryReaderdentro do VStack e isso causa problemas, que só posso resolver configurando o quadro GeometryReader, o que realmente não me ajuda, já que uso o leitor para definir o tamanho da visualização.

Aqui está o código sem ScrollView e funciona muito bem:

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

Isso resulta na seguinte imagem:

A etiqueta de tamanho personalizado deve ter largura total, mas metade da largura para a altura. Agora, se eu envolver o mesmo código em um ScrollView, isso acontece:

Não apenas tudo ficou menor, mas a altura do rótulo de tamanho personalizado é de alguma forma ignorada. Se eu definir a altura do GeometryReader, posso ajustar esse comportamento, mas quero que o GeometryReader cresça tanto quanto seu conteúdo. Como posso conseguir isso?

obrigado

Respostas

1 Asperi Oct 10 2020 at 20:02

Deve ser entendido que GeometryReadernão é uma ferramenta mágica, ela apenas lê o espaço disponível no contexto pai atual, mas ... ScrollViewnão tem espaço próprio disponível, é zero, porque determina o espaço necessário do conteúdo interno ... então usando GeometryReaderaqui você tem ciclo - o filho pergunta ao pai o tamanho, mas o pai espera o tamanho do filho ... O renderizador SwiftUI de alguma forma resolve isso (encontrando tamanhos mínimos conhecidos), apenas para não travar.

Aqui está uma solução possível para o seu cenário - o instrumento apropriado aqui são as preferências de visualização. Preparado e testado com 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()
    }
}

Nota: ... e não use GeometryReader se você não tiver certeza sobre o contexto em que sua visualização está.