代码之家  ›  专栏  ›  技术社区  ›  HeWhoRemains

如何在SwiftUI中创建圆形文本,根据度数变化从一条直线变为一个完美的圆?

  •  0
  • HeWhoRemains  · 技术社区  · 8 月前

    我试图在SwiftUI中创建一个圆形文本视图,其中文本以一条平线开始,当我使用滑块更改弧度时,当弧度达到360度时,它会逐渐形成一个完美的圆。以下是我目前所掌握的:

    import SwiftUI
    
    struct CirculerTextView: View {
        @State private var arcDegrees: Double = 0
        let text: String = "Happy Mornings"
        
        var body: some View {
            VStack(spacing: 40) {
                GeometryReader { geometry in
                    CirculerText(text: text, arcDegrees: arcDegrees, size: geometry.size)
                        .frame(height: 200)
                }
                .frame(height: 200)
                .border(Color.gray.opacity(0.3))
                
                VStack {
                    Slider(value: $arcDegrees, in: 0...360, step: 1)
                        .padding(.horizontal)
                    Text("Arc Degrees: \(Int(arcDegrees))")
                        .font(.subheadline)
                }
            }
            .padding()
        }
    }
    
    struct CirculerText: View {
        let text: String
        let arcDegrees: Double
        let size: CGSize
        
        var body: some View {
            let letters = Array(text)
            let letterCount = letters.count
            let chordLength = size.width * 0.5
            let spacing = letterCount > 1 ? chordLength / CGFloat(letterCount - 1) : 0
            let startX = (size.width - chordLength) / 2
            let centerY = size.height / 2
            let centerX = size.width / 2
            
            let totalArcRad = arcDegrees * .pi / 360
            let safeArcRad = max(abs(totalArcRad), 0.0001)
            let radius = chordLength / (2 * CGFloat(sin(safeArcRad / 2)))
            let circleCenter = CGPoint(x: centerX, y: centerY)
            
            return ZStack {
                ForEach(0..<letterCount, id: \.self) { index in
                    let letterAngle = -totalArcRad / 2 + totalArcRad * Double(index) / Double(letterCount - 1)
                    let x = circleCenter.x + radius * CGFloat(sin(letterAngle))
                    let y = circleCenter.y - radius * CGFloat(cos(letterAngle))
                    
                    let finalPos = CGPoint(x: x, y: y)
                    let textRotation = arcDegrees == 0 ? 0 : -angleInDegrees(letterAngle)
                    
                    Text(String(letters[index]))
                        .font(.title)
                        .position(finalPos)
                        .rotationEffect(.degrees(textRotation))
                }
            }
        }
        
        func angleInDegrees(_ radians: Double) -> Double {
            return radians * 360 / .pi
        }
    }
    
    struct CirculerTextView_Previews: PreviewProvider {
        static var previews: some View {
            CirculerTextView()
        }
    }
    
    

    这个想法是,当我移动滑块时,随着arcDegrees值从0增加到360,文本应该逐渐从一条直线过渡到一个完整的圆。目前,它适用于创建文本的圆形排列,但我很难让它平稳过渡,尤其是在度数较小(小于360度)的情况下,文本仍然看起来笔直。

    我如何修改代码,以便:

    文本从平直线平滑过渡到圆弧。 当度数达到360度时,文本完美地形成一个圆圈。 任何帮助都将不胜感激!

    我试图实现的目标

    enter image description here

    我现在得到的结果是什么

    enter image description here

    1 回复  |  直到 8 月前
        1
  •  1
  •   Benzy Neez    8 月前

    可以在以下答案中找到用于弯曲文本的SwiftUI解决方案 SwiftUI: How to have equal spacing between letters in a curved text view? (这是我的回答)。

    该解决方案将圆半径作为参数,根据该参数计算角度。这是一个改编版本,它使用您的命名并将角度作为参数,从中计算半径。角度为0需要特殊处理,因为半径将是无穷大。

    // Adaption of CurvedText, see
    // https://stackoverflow.com/a/77280669/20386264
    // for code comments and more explanation
    struct CircularText: View {
        let text: String
        let arcAngle: Angle
    
        var body: some View {
            if arcAngle.radians == 0.0 {
                textAsChars
            } else {
                textAsChars
                    .fixedSize()
                    .hidden()
                    .overlay {
                        GeometryReader { fullText in
                            let textWidth = fullText.size.width
                            let startAngle = -(arcAngle.radians / 2)
                            let radius = textWidth / arcAngle.radians
                            HStack(spacing: 0) {
                                ForEach(Array(text.enumerated()), id: \.offset) { index, character in
                                    Text(String(character))
                                        .hidden()
                                        .overlay {
                                            GeometryReader { charSpace in
                                                let midX = charSpace.frame(in: .named("FullText")).midX
                                                let fraction = midX / textWidth
                                                let angle = startAngle + (fraction * arcAngle.radians)
                                                let xOffset = (textWidth / 2) - midX
                                                Text(String(character))
                                                    .offset(y: -radius)
                                                    .rotationEffect(.radians(angle))
                                                    .offset(x: xOffset, y: radius)
                                            }
                                        }
                                }
                            }
                            .fixedSize()
                            .frame(width: textWidth)
                        }
                    }
                    .coordinateSpace(name: "FullText")
            }
        }
    
        private var textAsChars: some View {
            HStack(spacing: 0) {
                ForEach(Array(text.enumerated()), id: \.offset) { index, character in
                    Text(String(character))
                }
            }
        }
    }
    

    示例用法:

    struct CircularTextView: View {
        @State private var arcDegrees: Double = 0
        let text: String = "The quick brown fox jumps over the lazy dog"
    
        var body: some View {
            VStack {
                CircularText(text: text, arcAngle: .degrees(arcDegrees))
                    .frame(height: 300)
                    .border(Color.gray.opacity(0.3))
                Slider(value: $arcDegrees, in: -360...360, step: 1)
                    .padding(.horizontal)
                Text("Arc Degrees: \(Int(arcDegrees))")
                    .font(.subheadline)
            }
            .padding()
        }
    }
    

    请注意,a GeometryReader 不需要,因为文本采用其自然宽度。

    Animation