SwiftUI .rotationEffect () encuadre y compensación

8

Al aplicar .rotationEffect () a un Texto, gira el texto como se esperaba, pero su marco permanece sin cambios. Esto se convierte en un problema al apilar vistas rotadas con vistas no rotadas, como con un VStack de HStack, lo que hace que se superpongan.

Inicialmente pensé que el efecto de rotación simplemente actualizaría el marco del texto para que sea vertical, pero este no es el caso.

He intentado configurar manualmente el tamaño del marco y (si es necesario, compensar) el Texto, que funciona, pero no me gusta esta solución porque requiere algunas conjeturas y comprobaciones sobre dónde aparecerá el Texto, qué tan grande hacer el marco, etc.

¿Es así como se hace el texto rotado, o hay una solución más elegante para esto?

struct TextAloneView: View {

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

Texto superpuesto

EZhou00
fuente

Respuestas:

6

Debe ajustar el marco usted mismo en este caso. Eso requiere capturar qué es el marco y luego aplicar el ajuste.

Primero, para capturar el marco existente, cree una preferencia , que es un sistema para pasar datos de las vistas secundarias a sus padres:

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

Esto crea un nuevo .captureSize(in: $binding)método en Vistas.

Usando eso, podemos crear un nuevo tipo de Vista que rota su marco:

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

Y por conveniencia, una extensión para aplicarlo:

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

Y ahora su código debería funcionar como espera:

struct TextAloneView: View {

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

RotationEffect toma un segundo argumento que es el punto de anclaje, si lo omite, el valor predeterminado es .center.

Intenta esto en su lugar:

.rotationEffect(.degrees(-90), anchor: .bottomTrailing)
LuLuGaGa
fuente
44
¿Hay alguna razón por la que .rotationEffect () deje el marco sin cambios? Por ejemplo, si intercalo el texto girado entre dos líneas de texto horizontales: arriba y debajo, ¿por qué el VStack coloca los textos como si fueran todos horizontales?
EZhou00