SwiftUI:如何在弯曲文本视图中的字母之间具有相等的间距?

问题描述 投票:0回答:1

我正在绘制一个弯曲的文本视图,我希望每个字母之间的间距相等。

到目前为止,我尝试将字体更新为

.font(.system(size: 14, design: .monospaced))
,这使字母之间的间距相等,但它不是正确的字体。

目前,间距如下所示:

理想情况下,我希望间距看起来像这样(使用正确的自定义字体):

struct ContentView: View {

    @State private var letterWidths: [Int: Double] = [:]
    private let title = "AVENIRNEXT"

    var body: some View {
        ZStack {
            ForEach(Array(title.enumerated()), id: \.offset) { index, letter in
                VStack {
                    Text(String(letter))
                        .font(.custom("AvenirNext-DemiBold", size: 14))
                        .kerning(3)
                        .background(
                            GeometryReader { geometry in // using this to get the width of each letter
                                Color.clear
                                    .preference(
                                        key: LetterWidthPreferenceKey.self,
                                        value: geometry.size.width
                                    )
                            }
                        )
                        .onPreferenceChange(LetterWidthPreferenceKey.self, perform: { width in
                            letterWidths[index] = width
                        })
                    Spacer()
                }
                .rotationEffect(fetchAngle(at: index))
            }
            .frame(width: 300, height: 300 * 0.75)
            .rotationEffect(.degrees(335))
        }
    }

    func fetchAngle(at letterPosition: Int) -> Angle {
        let timesPi: (Double) -> Double = { $0 * .pi }

        let radius: Double = 125
        let circumference = timesPi(radius)

        let finalAngle = timesPi(
            letterWidths
                .filter { $0.key < letterPosition }
                .map(\.value)
                .reduce(0, +) / circumference
        )
        return .radians(finalAngle)
    }

}

struct LetterWidthPreferenceKey: PreferenceKey {
    static var defaultValue: Double = 0
    static func reduce(value: inout Double, nextValue: () -> Double) {
        value = nextValue()
    }
}
swiftui textview
1个回答
0
投票

这里为您提供了一个解决方案,它使用覆盖来获取基础文本的框架大小,并获取文本中每个字符的框架大小和相对位置。有了这些信息,就可以计算每个字符所需的角度和偏移量。

结果始终以垂直轴为中心,因此如果您需要以不同角度显示弯曲文本,只需旋转结果即可。

struct CurvedText: View {
    let string: String
    let radius: CGFloat

    var body: some View {

        // Show the full text in plain form, hidden
        Text(string)
            .lineLimit(1)
            .fixedSize()
            .hidden()
            .overlay {
                GeometryReader { fullText in
                    let textWidth = fullText.size.width
                    let arcAngle = radius == 0 ? 0 : (textWidth / radius)
                    let startAngle = -(arcAngle / 2)

                    // Build the text using single characters
                    HStack(spacing: 0) {
                        ForEach(Array(string.enumerated()), id: \.offset) { index, character in

                            // Each character in the HStack is hidden
                            Text(String(character))
                                .hidden()
                                .overlay {

                                    // Overlay with the same character, this time
                                    // visible and with rotation and offset
                                    GeometryReader { charSpace in
                                        let frame = charSpace.frame(in: .named("FullText"))
                                        let fraction = frame.midX / textWidth
                                        let angle = startAngle + (fraction * arcAngle)
                                        let xOffset = (textWidth / 2) - frame.midX
                                        Text(String(character))
                                            .offset(y: -radius)
                                            .rotationEffect(.radians(angle))
                                            .offset(x: xOffset)
                                    }
                                }
                        }
                    }
                    .fixedSize()
                    .frame(width: textWidth)
                }
            }
            .coordinateSpace(name: "FullText")
            .offset(y: radius)
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            CurvedText(
                string: "The quick brown fox",
                radius: 120
            )

            CurvedText(
                string: "jumps over the lazy dog",
                radius: 120
            )
            .font(.footnote)

            ZStack {
                CurvedText(
                    string: "AvenirNext-DemiBold",
                    radius: 100
                )
                .font(.custom("AvenirNext-DemiBold", size: 14))
                .kerning(3)
                .padding(.leading, 150)
                .padding(.bottom, 75)

                CurvedText(
                    string: "AvenirNext-DemiBold",
                    radius: 100
                )
                .font(.custom("AvenirNext-DemiBold", size: 14))
                .kerning(3)
                .rotationEffect(.degrees(-90))
                .padding(.trailing, 150)
            }
            .padding(.top, 100)
            .padding(.trailing, 75)
        }
    }
}

CurvedText

© www.soinside.com 2019 - 2024. All rights reserved.