SwiftUI .rotationEffect() framing and offsetting

后端 未结 2 714
盖世英雄少女心
盖世英雄少女心 2021-02-13 16:26

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 v

相关标签:
2条回答
  • 2021-02-13 16:29

    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)
    
    0 讨论(0)
  • 2021-02-13 16:43

    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()
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题