1.4 C
Canberra
Wednesday, July 23, 2025

ios – Tips on how to Convert SwiftUI Textual content to a Path and Apply Transformations?


Tips on how 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 method:

//
//  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 = Coloration.black
    @State non-public var strokeColor = Coloration.pink
    @State non-public var alignment: NSTextAlignment = .heart

    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: "Hiya, 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(top: 200)
            .padding()

            Divider().padding()

            // Sliders
            VStack {
                SliderView(worth: $fontSize, label: "Font Measurement", 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(.prime, 10)

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

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

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

                VStack {
                    Textual content("Stroke Coloration")
                    ColorPicker("", choice: $strokeColor)
                        .body(width: 50)
                }
            }
            .padding(.prime, 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,
                shade: 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 shade.
                    Path(path)
                        .fill(Coloration(fontColor))
                    // Then stroke the trail with the chosen stroke shade.
                    Path(path)
                        .stroke(Coloration(strokeColor), lineWidth: strokeWidth)
                }
                .body(width: geometry.dimension.width, top: geometry.dimension.top)
                // Regulate 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,
    shade: 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 method.
    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 one of the best ways to align and rework the generated textual content path appropriately in SwiftUI?

enter image description here

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

[td_block_social_counter facebook="tagdiv" twitter="tagdivofficial" youtube="tagdiv" style="style8 td-social-boxed td-social-font-icons" tdc_css="eyJhbGwiOnsibWFyZ2luLWJvdHRvbSI6IjM4IiwiZGlzcGxheSI6IiJ9LCJwb3J0cmFpdCI6eyJtYXJnaW4tYm90dG9tIjoiMzAiLCJkaXNwbGF5IjoiIn0sInBvcnRyYWl0X21heF93aWR0aCI6MTAxOCwicG9ydHJhaXRfbWluX3dpZHRoIjo3Njh9" custom_title="Stay Connected" block_template_id="td_block_template_8" f_header_font_family="712" f_header_font_transform="uppercase" f_header_font_weight="500" f_header_font_size="17" border_color="#dd3333"]
- Advertisement -spot_img

Latest Articles