Comment obtenir la taille et la position réelles de la vue dans SwiftUI?

Aug 15 2020

La question est de savoir comment obtenir la taille et la position du rendu réel affiché dans une vue parent? En d'autres termes, comment obtenir la Text("Foo")taille réelle dans le code SwiftUI ci-dessous?

GeometryReaderpeut être utilisé pour obtenir l' sizeinsertion de la zone de sécurité proposée par le parent via safeAreaInsetset ces informations sont définies à l'intérieur GeometryProxy. Vous pouvez voir sur la capture d'écran ci-dessous, la taille proposée VStackest la 300largeur et la 300hauteur et la taille réelle du VStackest inconnue.

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

Réponses

2 Metropolis Aug 15 2020 at 12:17

Taille réelle du rendu

La solution de contournement consiste à obtenir la taille réelle du rendu via un .backgroundmodificateur avec un GeometryReader. Les informations de taille à l'intérieur du nouveau proxy de géométrie peuvent ensuite être stockées dans une @Statevariable temporaire définie dans la vue.

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

Position réelle rendue

La position de rendu réelle de la vue peut être calculée à l'aide de Anchoret anchorPreference. En utilisant l'ancre et le parent geometryProxy, nous pouvons facilement obtenir les .boundinformations de position de la vue cible.

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