SwiftUI .rotationEffect () Framing und Offset

8

Wenn Sie .rotationEffect () auf einen Text anwenden, wird der Text wie erwartet gedreht, der Rahmen bleibt jedoch unverändert. Dies wird zu einem Problem, wenn gedrehte Ansichten mit nicht gedrehten Ansichten gestapelt werden, z. B. mit einem VStack von HStack, wodurch sich diese überlappen.

Anfangs dachte ich, der Rotationseffekt würde einfach den Rahmen des Textes so aktualisieren, dass er vertikal ist, aber das ist nicht der Fall.

Ich habe versucht, die Rahmengröße manuell einzustellen und (falls erforderlich, den Text zu versetzen), was funktioniert, aber diese Lösung gefällt mir nicht, da einige Vermutungen und Überprüfungen erforderlich sind, wo der Text angezeigt wird und wie groß er sein soll der Rahmen usw.

Wird rotierter Text nur so ausgeführt, oder gibt es dafür eine elegantere Lösung?

struct TextAloneView: View {

    var body: some View {
        VStack {
            Text("Horizontal text")
            Text("Vertical text").rotationEffect(.degrees(-90))
        }
    }
}

Überlappender Text

EZhou00
quelle

Antworten:

6

In diesem Fall müssen Sie den Rahmen selbst anpassen. Dazu muss der Rahmen erfasst und anschließend die Anpassung vorgenommen werden.

Um den vorhandenen Frame zu erfassen, erstellen Sie zunächst eine Voreinstellung , bei der Daten aus untergeordneten Ansichten an die Eltern übergeben werden:

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

extension View {
    func captureSize(in binding: Binding<CGSize>) -> some View {
        overlay(GeometryReader { proxy in
            Color.clear.preference(key: SizeKey.self, value: proxy.size)
        })
            .onPreferenceChange(SizeKey.self) { size in binding.wrappedValue = size }
    }
}

Dadurch wird eine neue .captureSize(in: $binding)Methode für Ansichten erstellt.

Auf diese Weise können wir eine neue Art von Ansicht erstellen, die den Rahmen dreht:

struct Rotated<Rotated: View>: View {
    var view: Rotated
    var angle: Angle

    init(_ view: Rotated, angle: Angle = .degrees(-90)) {
        self.view = view
        self.angle = angle
    }

    @State private var size: CGSize = .zero

    var body: some View {
        // Rotate the frame, and compute the smallest integral frame that contains it
        let newFrame = CGRect(origin: .zero, size: size)
            .offsetBy(dx: -size.width/2, dy: -size.height/2)
            .applying(.init(rotationAngle: CGFloat(angle.radians)))
            .integral

        return view
            .fixedSize()                    // Don't change the view's ideal frame
            .captureSize(in: $size)         // Capture the size of the view's ideal frame
            .rotationEffect(angle)          // Rotate the view
            .frame(width: newFrame.width,   // And apply the new frame
                   height: newFrame.height)
    }
}

Und der Einfachheit halber eine Erweiterung, um es anzuwenden:

extension View {
    func rotated(_ angle: Angle = .degrees(-90)) -> some View {
        Rotated(self, angle: angle)
    }
}

Und jetzt sollte Ihr Code wie erwartet funktionieren:

struct TextAloneView: View {

    var body: some View {
        VStack {
            Text("Horizontal text")
            Text("Vertical text").rotated()
        }
    }
}
Rob Napier
quelle
0

RotationEffect verwendet ein zweites Argument, das der Ankerpunkt ist, wenn Sie es weglassen - der Standardwert ist .center.

Versuchen Sie stattdessen Folgendes:

.rotationEffect(.degrees(-90), anchor: .bottomTrailing)
LuLuGaGa
quelle
4
Gibt es einen Grund, warum .rotationEffect () den Frame unverändert lässt? Wenn ich beispielsweise den gedrehten Text zwischen zwei horizontalen Textzeilen einbaue: darüber und darunter, warum platziert der VStack die Texte so, als ob sie alle horizontal wären?
EZhou00