¿Cómo obtener el tamaño y la posición reales de la vista en SwiftUI?

Aug 15 2020

La pregunta es cómo obtener el tamaño y la posición reales visualizados en una vista principal. En otras palabras, ¿cómo obtener el Text("Foo")tamaño real en el código SwiftUI a continuación?

GeometryReaderse puede utilizar para sizeinsertar la propuesta principal y el área segura mediante safeAreaInsetsy esta información se define en el interior GeometryProxy. Puede ver en la captura de pantalla a continuación que el tamaño propuesto VStackes 300ancho y 300alto y VStackse desconoce el tamaño real .

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

Respuestas

2 Metropolis Aug 15 2020 at 12:17

Tamaño real renderizado

La solución es obtener el tamaño real renderizado mediante un .backgroundmodificador con un archivo GeometryReader. La información de tamaño dentro del nuevo proxy de geometría se puede almacenar en una @Statevariable temporal definida en la 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)
    }
}

Posición real renderizada

La posición real renderizada para la vista se puede calcular usando Anchory anchorPreference. Usando el ancla y el padre geometryProxy, podemos obtener fácilmente la .boundinformación de posición de la vista 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)
    }
}