admin管理员组

文章数量:1391925

I'm trying to create a circular text path in SwiftUI using a custom CircularTextPath view. The text is being drawn on an arc, and I want to add a background color behind the path, as shown in another image I have (for example, adding a solid background color to the entire text arc).

CircularTextPath:

import SwiftUI

struct CircularTextPath: View {
    let text: String
    let arcAngle: Angle
    
    var body: some View {
        if arcAngle.radians != 0.0 {
            textAsPath
                .fixedSize()
                .hidden()
                .overlay {
                    GeometryReader { fullText in
                        let textWidth = fullText.size.width
                        let startAngle = -(arcAngle.radians / 2)
                        let radius = arcAngle.radians > 0 ?  textWidth * 1.5 / arcAngle.radians : textWidth * 2.0 / 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
                                            TextPath(character: String(character))
                                                .scaleEffect(x: 1, y: 1) // Flip horizontally to correct mirroring
                                                .offset(y: -radius)
                                                .rotationEffect(.radians(angle))
                                                .offset(x: xOffset, y: radius)
                                        }
                                    }
                            }
                        }
                        .fixedSize()
                        .frame(width: textWidth)
                    }
                }
                .coordinateSpace(name: "FullText")
        } else {
            textAsPath
        }
    }
    
    private var textAsPath: some View {
        HStack(spacing: 0) {
            ForEach(Array(text.enumerated()), id: \.offset) { index, character in
                TextPath(character: String(character))
                    .scaleEffect(x: 1, y: 1) // Flip horizontally to correct mirroring
            }
        }
    }
}

struct TextPath: View {
    let character: String
    
    var body: some View {
        let path = characterToPath(character)
        return path.fill(Color.primary)
    }
    
    private func characterToPath(_ character: String) -> Path {
        let font = UIFont.systemFont(ofSize: 20)
        let attributedString = NSAttributedString(string: character, attributes: [.font: font])
        let line = CTLineCreateWithAttributedString(attributedString)
        let path = CGMutablePath()
        
        for run in (CTLineGetGlyphRuns(line) as! [CTRun]) {
            for index in 0..<CTRunGetGlyphCount(run) {
                var glyph = CGGlyph()
                var position = CGPoint()
                CTRunGetGlyphs(run, CFRangeMake(index, 1), &glyph)
                CTRunGetPositions(run, CFRangeMake(index, 1), &position)
                if let letterPath = CTFontCreatePathForGlyph(font, glyph, nil) {
                    let transform = CGAffineTransform(translationX: position.x, y: position.y).scaledBy(x: 1, y: -1) // Flip horizontally
                    path.addPath(letterPath, transform: transform)
                }
            }
        }
        return Path(path)
    }
}




What I want to achieve: I want to add a background color behind the path, specifically behind the entire circular arc of text. How can I modify this code to add a solid background color to the path, similar to the effect shown in the other image I have?

What I've tried so far:

  • I’ve tried using .background(Color) or .overlay(Color) to add a background to the - - - TextPath, but these don’t seem to achieve the desired effect.
  • I’ve also tried adding a background color to the path itself, but that didn’t work out because the path isn't a solid object, it's just the outline of the text.

what output I got

output I want

what I want to do

when two or three lines

I'm trying to create a circular text path in SwiftUI using a custom CircularTextPath view. The text is being drawn on an arc, and I want to add a background color behind the path, as shown in another image I have (for example, adding a solid background color to the entire text arc).

CircularTextPath:

import SwiftUI

struct CircularTextPath: View {
    let text: String
    let arcAngle: Angle
    
    var body: some View {
        if arcAngle.radians != 0.0 {
            textAsPath
                .fixedSize()
                .hidden()
                .overlay {
                    GeometryReader { fullText in
                        let textWidth = fullText.size.width
                        let startAngle = -(arcAngle.radians / 2)
                        let radius = arcAngle.radians > 0 ?  textWidth * 1.5 / arcAngle.radians : textWidth * 2.0 / 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
                                            TextPath(character: String(character))
                                                .scaleEffect(x: 1, y: 1) // Flip horizontally to correct mirroring
                                                .offset(y: -radius)
                                                .rotationEffect(.radians(angle))
                                                .offset(x: xOffset, y: radius)
                                        }
                                    }
                            }
                        }
                        .fixedSize()
                        .frame(width: textWidth)
                    }
                }
                .coordinateSpace(name: "FullText")
        } else {
            textAsPath
        }
    }
    
    private var textAsPath: some View {
        HStack(spacing: 0) {
            ForEach(Array(text.enumerated()), id: \.offset) { index, character in
                TextPath(character: String(character))
                    .scaleEffect(x: 1, y: 1) // Flip horizontally to correct mirroring
            }
        }
    }
}

struct TextPath: View {
    let character: String
    
    var body: some View {
        let path = characterToPath(character)
        return path.fill(Color.primary)
    }
    
    private func characterToPath(_ character: String) -> Path {
        let font = UIFont.systemFont(ofSize: 20)
        let attributedString = NSAttributedString(string: character, attributes: [.font: font])
        let line = CTLineCreateWithAttributedString(attributedString)
        let path = CGMutablePath()
        
        for run in (CTLineGetGlyphRuns(line) as! [CTRun]) {
            for index in 0..<CTRunGetGlyphCount(run) {
                var glyph = CGGlyph()
                var position = CGPoint()
                CTRunGetGlyphs(run, CFRangeMake(index, 1), &glyph)
                CTRunGetPositions(run, CFRangeMake(index, 1), &position)
                if let letterPath = CTFontCreatePathForGlyph(font, glyph, nil) {
                    let transform = CGAffineTransform(translationX: position.x, y: position.y).scaledBy(x: 1, y: -1) // Flip horizontally
                    path.addPath(letterPath, transform: transform)
                }
            }
        }
        return Path(path)
    }
}




What I want to achieve: I want to add a background color behind the path, specifically behind the entire circular arc of text. How can I modify this code to add a solid background color to the path, similar to the effect shown in the other image I have?

What I've tried so far:

  • I’ve tried using .background(Color) or .overlay(Color) to add a background to the - - - TextPath, but these don’t seem to achieve the desired effect.
  • I’ve also tried adding a background color to the path itself, but that didn’t work out because the path isn't a solid object, it's just the outline of the text.

what output I got

output I want

what I want to do

when two or three lines

Share Improve this question edited Mar 13 at 5:00 HeWhoRemains asked Mar 12 at 11:52 HeWhoRemainsHeWhoRemains 6511 bronze badges 5
  • I only know cgPath of that text , that is Path – HeWhoRemains Commented Mar 12 at 12:04
  • can you write a simple demo , what are you saying – HeWhoRemains Commented Mar 12 at 12:08
  • I used that way but I need result in Path so I just converted it to path – HeWhoRemains Commented Mar 12 at 12:18
  • Let us continue this discussion in chat. – HeWhoRemains Commented Mar 12 at 12:36
  • Let us continue this discussion in chat. – HeWhoRemains Commented Mar 12 at 12:37
Add a comment  | 

1 Answer 1

Reset to default 2

Converting a path that contains the outline of letters to an arc may be difficult. However, maybe there is an easier way.

If you know the arc angle and the radius then you can simply trim the path of a circle. Your example includes these values, so you should be able to stroke the trimmed circle without having to convert the text to a path.

Here is an example to show it working. I am using CurvedText for this, as I was suggesting you use in my answer to your earlier question.

let arcDegrees: Double = 320
let radius: CGFloat = 90
let fontSize: CGFloat = 50
let strokeWidth: CGFloat = 70

var body: some View {
    ZStack {
        let endTrimFraction = (360.0 - arcDegrees) / (2 * 360.0)
        Circle()
            .trim(from: endTrimFraction, to: 1 - endTrimFraction)
            .stroke(.black, lineWidth: strokeWidth)
            .rotationEffect(.degrees(-90))
            .padding(fontSize / 2)

        // See https://stackoverflow/a/77280669/20386264
        CurvedText(string: "I am he who remains", radius: -radius)
            .offset(y: radius)
            .font(.system(size: fontSize, weight: .black))
            .foregroundStyle(.white)
    }
    .frame(width: (2 * radius) + fontSize, height: (2 * radius) + fontSize)
}


EDIT If I understand correctly, what you are aiming for is a way to vary the arc angle of some text dynamically, exactly as was being done in your earlier question, but also to show a colored background behind the text. The background should fit the text as the angle is changed.

So, based on the approach shown above, here is an adaption of the answer I previously provided to your other question. The main changes:

  • The view CircularText now accepts a binding to a variable which is used to record the computed radius (4 lines of code added, no other changes).
  • The parent view CircularTextView displays the background behind the curved text.

I found that it looks best if the background is slightly wider than the text. The easiest way to achieve this is to add leading and trailing spaces to the text being shown.

struct CircularTextView: View {
    @State private var arcDegrees: Double = 0
    @State private var textRadius = CGFloat.zero
    let text: String = " I am he who remains " // 

本文标签: