ios – How one can Convert SwiftUI Textual content to a Path and Apply Transformations?

ios – How one can Convert SwiftUI Textual content to a Path and Apply Transformations?


How one can 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 consequence 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 = Coloration.black
    @State non-public var strokeColor = Coloration.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: "Hi there, 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("Heart").tag(NSTextAlignment.middle)
                Textual content("Proper").tag(NSTextAlignment.proper)
            }
            .pickerStyle(SegmentedPickerStyle())
            .padding(.high, 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(.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,
                coloration: fontColor,
                strokeColor: strokeColor,
                strokeWidth: strokeWidth,
                alignment: alignment,
                letterSpacing: letterSpacing,
                isBold: isBold,
                isItalic: isItalic,
                isUnderlined: isUnderlined,
                isCurved: isCurved,
                curveRadius: curveRadius
            ) {
                ZStack {
                    // Fill the whole textual content path with the chosen font coloration.
                    Path(path)
                        .fill(Coloration(fontColor))
                    // Then stroke the trail with the chosen stroke coloration.
                    Path(path)
                        .stroke(Coloration(strokeColor), lineWidth: strokeWidth)
                }
                .body(width: geometry.measurement.width, peak: geometry.measurement.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,
    coloration: 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, measurement: 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 one of the best ways to align and remodel the generated textual content path appropriately in SwiftUI?

enter image description here

Leave a Reply

Your email address will not be published. Required fields are marked *