在 SwiftUI 中旋转文本视图及其框架

wig*_*ing 5 macos swift swiftui

我可以使用 rotationEffect 在 SwiftUI 中旋转文本,但它不会旋转框架。如图所示,文本被旋转,但框架仍然是水平的。我也想旋转框架,这样它就不会占用水平空间。这是一个 Mac 应用程序,我使用 HStack 来防止窗口更改大小时文本和圆形视图重叠。

竖排文字

import SwiftUI

struct ContentView: View {
    var body: some View {
        HStack {
            Text("Vertical text")
                .rotationEffect(.degrees(-90))
            Circle()
        }
        .frame(width: 400, height: 300)
    }
}
Run Code Online (Sandbox Code Playgroud)

一个建议是使用 ZStack。这修复了圆形旁边的文本视图的外观,但它不会旋转文本视图的框架。如果 ZStack 与可调整大小的窗口一起使用,那么 Circle 可以与 Text 视图重叠,这就是我在原始示例中尝试使用 HStack 的原因。

可调整大小的窗口

struct ContentView: View {
    var body: some View {        
        ZStack(alignment: .leading) {
            Text("Vertical text")
                .rotationEffect(.degrees(-90))
            Circle()
                .padding(.leading)
        }
        .frame(minWidth: 400, minHeight: 300)
    }
}
Run Code Online (Sandbox Code Playgroud)

wig*_*ing 11

将固定大小和框架大小应用于文本视图似乎可以解决我的问题。这也适用于可调整大小的窗口,因为 HStack 可以防止文本视图和圆形视图重叠。

在此输入图像描述

import SwiftUI

struct ContentView: View {
    var body: some View {
        HStack {
            Text("Vertical text")
                .rotationEffect(.degrees(-90))
                .fixedSize()
                .frame(width: 20, height: 180)
            Circle()
                .frame(width: 200)
        }
        .frame(width: 400, height: 300)
    }
}
Run Code Online (Sandbox Code Playgroud)


nte*_*iss 6

使用 afixedSize()然后显式frame修饰符是正确的方法,但您不想Text每次更改包含文本时都必须猜测并检查更新您的框架。我们可以使用自定义视图修饰符和首选项系统来将精确正确的尺寸传递到视图层次结构中

在此输入图像描述


struct RotatedVertical: ViewModifier {

    /// Whether the view should be rotated so its baseline ends up on the left is `true`, right if `false.`
    let baselineLeft: Bool = false

    /// A binding to a `State` variable that gets assigned to the caller's frame size.
    let frame: Binding<CGSize?>

    func body(content: Content) -> some View {
        content.overlay(GeometryReader { proxy in
            Color.clear.preference(key: TextSizeKey.self, value: proxy.size)
        })
        .onPreferenceChange(TextSizeKey.self, perform: { newSize in
            frame.wrappedValue = newSize
        })
        .fixedSize() // Calculate and fix our ideal size before rotation
        .rotationEffect(baselineLeft ? .degrees(90) : .degrees(270))
        .frame(width: frame.wrappedValue?.height, height: frame.wrappedValue?.width) // Assign the flipped sizes via the Preference system updates
    }
}

extension View {

    /// Wrap our `ViewModifier` in a convenience method
    func rotatedVertical(baselineLeft: Bool = false, frame: Binding<CGSize?>) -> some View {
        modifier(RotatedVertical(frame: frame))
    }
}

struct RotatedTextView: View {
    @State var textSize: CGSize?

    var body: some View {
        Text("Hello World")
            .rotatedVertical(frame: $textSize)
            .border(.green)
    }
}
Run Code Online (Sandbox Code Playgroud)

  • RotatedVertical 修饰符中的“TextSizeKey”到底是什么? (3认同)

Min*_*Liu 5

对于 iOS 16 及更高版本,我们可以收获新协议的力量Layout。该解决方案使布局正确,不执行状态更新,并且不需要您写下魔术数字

struct VerticalLabel: View {
    var text: Text

    var body: some View {
        VerticalLayout() {
            text
        }
        .rotationEffect(.degrees(-90))
    }

    private struct VerticalLayout: Layout {
        func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
            let size = subviews.first!.sizeThatFits(.unspecified)
            return .init(width: size.height, height: size.width)
        }

        func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
            subviews.first!.place(at: .init(x: bounds.midX, y: bounds.midY), anchor: .center, proposal: .unspecified)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

该代码定义了 a VerticalLayout,它要求Text其不受约束的大小(因此.unspecified)。然后它通过交换宽度和高度来返回尺寸。最后,它将文本放置在框架的中心。

视图VerticalLabel使用这种布局来达到旋转框架的效果。之后,调用rotationEffect视觉完成旋转。