SwiftUI .rotationEffect() framing and offsetting

自闭症网瘾萝莉.ら 提交于 2020-12-30 05:48:10

问题


When applying .rotationEffect() to a Text, it rotates the text as expected, but its frame remains unchanged. This becomes an issue when stacking rotated views with non-rotated views, such as with a VStack of HStack, causing them to overlap.

I initially thought the rotationEffect would simply update the frame of the Text to be vertical, but this is not the case.

I've tried manually setting the frame size and (if needed, offsetting) the Text, which sort of works, but I don't like this solution because it requires some guessing and checking of where the Text will appear, how big to make the frame, etc.

Is this just how rotated text is done, or is there a more elegant solution to this?

struct TextAloneView: View {

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

Overlapping Text


回答1:


You need to adjust the frame yourself in this case. That requires capturing what the frame is, and then applying the adjustment.

First, to capture the existing frame, create a preference, which is a system for passing data from child views to their parents:

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

This creates a new .captureSize(in: $binding) method on Views.

Using that, we can create a new kind of View that rotates its frame:

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

And for convenience, an extension to apply it:

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

And now your code should work as you expect:

struct TextAloneView: View {

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



回答2:


RotationEffect takes a second argument which is the anchor point, if you omit it - the default is .center.

Try this instead:

.rotationEffect(.degrees(-90), anchor: .bottomTrailing)


来源:https://stackoverflow.com/questions/58494193/swiftui-rotationeffect-framing-and-offsetting

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!