Wie erhalte ich die tatsächliche Größe und Position der gerenderten Ansicht in SwiftUI?

Aug 15 2020

Die Frage ist, wie die tatsächlich angezeigte gerenderte Größe und Position in einer übergeordneten Ansicht ermittelt werden kann. Mit anderen Worten, wie erhält man die tatsächliche Text("Foo")Größe im SwiftUI-Code unten?

GeometryReaderkann verwendet werden, um den übergeordneten vorgeschlagenen sizeund sicheren Bereich einzufügen, safeAreaInsetsund diese Informationen werden im Inneren definiert GeometryProxy. Sie können aus dem Screenshot unten, um die vorgeschlagene Größe VStackist 300Breite und 300Höhe und die tatsächliche Größe für die VStackist nicht bekannt.

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

Antworten

2 Metropolis Aug 15 2020 at 12:17

Tatsächliche gerenderte Größe

Die Problemumgehung besteht darin, die tatsächlich gerenderte Größe über einen .backgroundModifikator mit einem verschachtelten zu erhalten GeometryReader. Die Größeninformationen im neuen Geometrie-Proxy können dann in einer temporären @StateVariablen gespeichert werden, die in der Ansicht definiert ist.

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

Tatsächliche gerenderte Position

Die tatsächlich gerenderte Position für die Ansicht kann mit Anchorund berechnet werden anchorPreference. Mit dem Anker und dem übergeordneten geometryProxyElement können wir leicht die Positionsinformationen .boundder Zielansicht abrufen.

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