Como obter o tamanho e a posição reais da visualização renderizada no SwiftUI?

Aug 15 2020

A questão é como obter o tamanho real renderizado e a posição visualizada em uma visualização pai? Em outras palavras, como obter o Text("Foo")tamanho real no código SwiftUI abaixo?

GeometryReaderpode ser usado para sizeinserir a área segura e proposta pelos pais por meio de safeAreaInsetse essas informações são definidas internamente GeometryProxy. Você pode ver na captura de tela abaixo, o tamanho proposto para VStacké 300largura e 300altura e o tamanho real para VStacké desconhecido.

struct FooView: View {
    var body: some View {
        GeometryReader { geometryProxy in
            VStack {
                Text("\(geometryProxy.size.height), \(geometryProxy.size.width)")
                Text("Foo")
            }
            .background(Color.green)
        }
        .frame(width: 300, height: 300)
        .background(Color.blue)
    }
}

Respostas

2 Metropolis Aug 15 2020 at 12:17

Tamanho real renderizado

A solução alternativa é obter o tamanho real renderizado por meio do .backgroundmodificador com um aninhado GeometryReader. As informações de tamanho dentro do novo proxy de geometria podem ser armazenadas em uma @Statevariável temporária definida na Visualização.

struct FooSizePreferenceKey: PreferenceKey {
    static let defaultValue = CGSize.zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

struct FooView: View {
    @State private var fooSize: CGSize = .zero

    var body: some View {
        GeometryReader { geometryProxy in
            VStack {
                Text("\(self.fooSize.height), \(self.fooSize.width)")
                Text("Foo")
                    .background(
                        GeometryReader { fooProxy in
                            Color
                                .green
                                .preference(key: FooSizePreferenceKey.self,
                                            value: fooProxy.size)
                                .onPreferenceChange(FooSizePreferenceKey.self) { size in
                                    self.fooSize = size
                            }
                        }
                    )
            }
        }
        .frame(width: 300, height: 300)
        .background(Color.blue)
    }
}

Posição real renderizada

A posição real renderizada para a vista pode ser calculada usando Anchore anchorPreference. Usando a âncora e o pai geometryProxy, podemos obter facilmente as .boundinformações de posição da visualização de destino.

struct FooAnchorData: Equatable {
    var anchor: Anchor<CGRect>? = nil
    static func == (lhs: FooAnchorData, rhs: FooAnchorData) -> Bool {
        return false
    }
}

struct FooAnchorPreferenceKey: PreferenceKey {
    static let defaultValue = FooAnchorData()
    static func reduce(value: inout FooAnchorData, nextValue: () -> FooAnchorData) {
        value.anchor = nextValue().anchor ?? value.anchor
    }
}

struct FooView: View {
    @State private var foo: CGPoint = .zero

    var body: some View {
        GeometryReader { geometryProxy in
            VStack {
                Text("\(self.foo.x), \(self.foo.y)")
                Text("Foo")
                    .background(
                        GeometryReader { fooProxy in
                            Color
                                .green
                                .anchorPreference(key: FooAnchorPreferenceKey.self,
                                                  value: .bounds,
                                                  transform: {FooAnchorData(anchor: $0) })
                                .onPreferenceChange(FooAnchorPreferenceKey.self) { data in
                                    self.foo = geometryProxy[data.anchor!].origin
                            }
                        }
                    )
            }
        }
        .frame(width: 300, height: 300)
        .background(Color.blue)
    }
}