15.9 C
New York
Tuesday, March 11, 2025

ios – Methods to Convert SwiftUI Textual content to a Path and Apply Transformations?


Methods to Convert SwiftUI Textual content to a Path and Apply Transformations?

Physique:
I’m making an attempt to render a Textual content view as a path in SwiftUI in order that I can apply transformations like scaling, skewing, and rotation. I’ve managed to extract the glyphs utilizing Textual content.toPath(), however I’m dealing with points with making use of transformations and rendering the end result correctly.

Right here’s my present strategy:

//
//  TextToPathView.swift


import SwiftUI
import CoreText

struct TextToPathView: View {
    @State non-public var fontSize: CGFloat = 40
    @State non-public var strokeWidth: CGFloat = 2
    @State non-public var letterSpacing: CGFloat = 2
    @State non-public var curveRadius: CGFloat = 0

    @State non-public var isBold = false
    @State non-public var isItalic = false
    @State non-public var isUnderlined = false
    @State non-public var isCurved = false

    @State non-public var fontColor = Colour.black
    @State non-public var strokeColor = Colour.purple
    @State non-public var alignment: NSTextAlignment = .middle

    var physique: some View {
        VStack {
            Textual content("Textual content to Path Editor")
                .font(.title)
                .fontWeight(.daring)
                .padding(.backside, 10)

            // Use the unified PathView that creates one CGPath for all results.
            PathView(
                textual content: "Good day, SwiftUI!",
                fontSize: fontSize,
                strokeWidth: strokeWidth,
                letterSpacing: letterSpacing,
                curveRadius: curveRadius,
                isBold: isBold,
                isItalic: isItalic,
                isUnderlined: isUnderlined,
                isCurved: isCurved,
                fontColor: UIColor(fontColor),
                strokeColor: UIColor(strokeColor),
                alignment: alignment
            )
            .body(peak: 200)
            .padding()

            Divider().padding()

            // Sliders
            VStack {
                SliderView(worth: $fontSize, label: "Font Dimension", vary: 20...100)
                SliderView(worth: $strokeWidth, label: "Stroke Width", vary: 0...5)
                SliderView(worth: $letterSpacing, label: "Letter Spacing", vary: 0...10)
                if isCurved {
                    SliderView(worth: $curveRadius, label: "Curve Radius", vary: 50...200)
                }
            }

            // Toggles
            HStack {
                Toggle("Daring", isOn: $isBold)
                Toggle("Italic", isOn: $isItalic)
                Toggle("Underline", isOn: $isUnderlined)
            }
            .padding(.high, 10)

            Toggle("Curved Textual content", isOn: $isCurved)
                .padding(.high, 5)

            // Alignment Picker
            Picker("Alignment", choice: $alignment) {
                Textual content("Left").tag(NSTextAlignment.left)
                Textual content("Middle").tag(NSTextAlignment.middle)
                Textual content("Proper").tag(NSTextAlignment.proper)
            }
            .pickerStyle(SegmentedPickerStyle())
            .padding(.high, 5)

            // Colour Pickers
            HStack {
                VStack {
                    Textual content("Font Colour")
                    ColorPicker("", choice: $fontColor)
                        .body(width: 50)
                }

                VStack {
                    Textual content("Stroke Colour")
                    ColorPicker("", choice: $strokeColor)
                        .body(width: 50)
                }
            }
            .padding(.high, 10)
        }
        .padding()
    }
}

// MARK: - PathView (Creates one CGPath reflecting all results)
struct PathView: View {
    var textual content: String
    var fontSize: CGFloat
    var strokeWidth: CGFloat
    var letterSpacing: CGFloat
    var curveRadius: CGFloat
    var isBold: Bool
    var isItalic: Bool
    var isUnderlined: Bool
    var isCurved: Bool
    var fontColor: UIColor
    var strokeColor: UIColor
    var alignment: NSTextAlignment

    var physique: some View {
        GeometryReader { geometry in
            if let path = styledTextToPath(
                textual content: textual content,
                font: UIFont.systemFont(ofSize: fontSize),
                fontSize: fontSize,
                colour: fontColor,
                strokeColor: strokeColor,
                strokeWidth: strokeWidth,
                alignment: alignment,
                letterSpacing: letterSpacing,
                isBold: isBold,
                isItalic: isItalic,
                isUnderlined: isUnderlined,
                isCurved: isCurved,
                curveRadius: curveRadius
            ) {
                ZStack {
                    // Fill the entire textual content path with the chosen font colour.
                    Path(path)
                        .fill(Colour(fontColor))
                    // Then stroke the trail with the chosen stroke colour.
                    Path(path)
                        .stroke(Colour(strokeColor), lineWidth: strokeWidth)
                }
                .body(width: geometry.dimension.width, peak: geometry.dimension.peak)
                // Modify vertical offset as wanted.
                .offset(y: 50)
            }
        }
    }
}

// MARK: - Create a CGPath that displays all styling results.
func styledTextToPath(
    textual content: String,
    font: UIFont,
    fontSize: CGFloat,
    colour: UIColor,
    strokeColor: UIColor,
    strokeWidth: CGFloat,
    alignment: NSTextAlignment,
    letterSpacing: CGFloat,
    isBold: Bool,
    isItalic: Bool,
    isUnderlined: Bool,
    isCurved: Bool,
    curveRadius: CGFloat
) -> CGPath? {

    // Decide the font traits.
    var traits: UIFontDescriptor.SymbolicTraits = []
    if isBold { traits.insert(.traitBold) }
    if isItalic { traits.insert(.traitItalic) }

    var styledFont = font
    if let descriptor = font.fontDescriptor.withSymbolicTraits(traits) {
        styledFont = UIFont(descriptor: descriptor, dimension: fontSize)
    }

    // Construct widespread attributes.
    let attributes: [NSAttributedString.Key: Any] = [
        .font: styledFont,
        .foregroundColor: color,
        .kern: letterSpacing,
        .strokeColor: strokeColor,
        .strokeWidth: strokeWidth
    ]

    // If not curved, use a easy strategy.
    if !isCurved {
        let attributedString = NSAttributedString(string: textual content, attributes: attributes)
        let line = CTLineCreateWithAttributedString(attributedString)
        let totalWidth = CGFloat(CTLineGetTypographicBounds(line, nil, nil, nil))
        let runArray = CTLineGetGlyphRuns(line) as NSArray
        let path = CGMutablePath()

        for run in runArray {
            let run = run as! CTRun
            let rely = CTRunGetGlyphCount(run)
            for index in 0..

    var physique: some View {
        VStack {
            Textual content("(label): (Int(worth))")
            Slider(worth: $worth, in: vary)
        }
        .padding(.vertical, 5)
    }
}

// MARK: - Preview
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        TextToPathView()
    }
}

Points:

  • The textual content path doesn’t align correctly within the body.

  • Transformations like scaling and skewing don’t appear to work as anticipated.

  • The glyphs seem shifted when utilizing CGAffineTransform.

What’s the easiest way to align and remodel the generated textual content path accurately in SwiftUI?

enter image description here

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles