Come ottenere le dimensioni e la posizione effettive del rendering della vista in SwiftUI?

Aug 15 2020

La domanda è: come ottenere le dimensioni e la posizione effettive del rendering visualizzato in una vista principale? In altre parole, come ottenere la Text("Foo")dimensione effettiva nel codice SwiftUI di seguito?

GeometryReaderpuò essere utilizzato per inserire la proposta genitore sizee l'area sicura safeAreaInsetse queste informazioni sono definite all'interno GeometryProxy. Puoi vedere dallo screenshot qui sotto, la dimensione proposta VStackè 300larghezza e 300altezza e la dimensione effettiva per VStackè sconosciuta.

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

Risposte

2 Metropolis Aug 15 2020 at 12:17

Dimensioni effettive di rendering

La soluzione alternativa consiste nell'ottenere la dimensione effettiva del rendering tramite un .backgroundmodificatore con un file nidificato GeometryReader. Le informazioni sulla dimensione all'interno del nuovo proxy della geometria possono quindi essere memorizzate in una @Statevariabile temporanea definita nella Vista.

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

Posizione reale resa

La posizione di rendering effettiva per la vista può essere calcolata utilizzando Anchore anchorPreference. Usando l'ancora e il genitore geometryProxy, possiamo facilmente ottenere le .boundinformazioni sulla posizione della vista di destinazione.

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