ios – How can I be certain that letters are positioned upright alongside a user-drawn path?

ios – How can I be certain that letters are positioned upright alongside a user-drawn path?


I’m engaged on a SwiftUI view the place the consumer can draw a customized path, and I wish to place letters of a string alongside that path. The letters needs to be aligned perpendicularly to the trail, guaranteeing that the textual content seems to be standing upright alongside the drawn floor (whether or not the trail is straight, sloped, or curvy). Nonetheless, the angle of the letters is usually not as anticipated, particularly when the trail is sloped or curved.

Right here’s the code I’ve written to this point to attain this:

import SwiftUI

struct DraggingLettersView: View {
    @State non-public var letters: [DraggedLetter] = []
    @State non-public var currentIndex = 0
    @State non-public var dragPath: [CGPoint] = []
    
    non-public let longString = "Let me know in case you face any additional points! 😊"
    non-public let letterSpacing: CGFloat = 15

    var physique: some View {
        GeometryReader { geometry in
            ZStack {
                // Draw the drag path
                Path { path in
                    for (index, level) in dragPath.enumerated() {
                        if index == 0 {
                            path.transfer(to: level)
                        } else {
                            path.addLine(to: level)
                        }
                    }
                }
                .stroke(Colour.blue, lineWidth: 2)
                
                // Place letters alongside the trail
                ForEach(letters) { letter in
                    Textual content(String(letter.character))
                        .font(.system(measurement: 24, weight: .daring))
                        .foregroundColor(.white)
                        .rotationEffect(.levels(letter.angle))
                        .place(letter.place)
                }
                
                // Clear Button
                Button(motion: {
                    letters.removeAll()
                    currentIndex = 0
                    dragPath.removeAll()
                }, label: {
                    Textual content("Clear")
                        .body(width: geometry.measurement.width * 0.2 , top: geometry.measurement.top * 0.05)
                        .background(Colour.white)
                        .cornerRadius(10)
                        .shadow(radius: 5)
                })
                .place(x: geometry.measurement.width - 60, y: 80)
            }
            .body(width: geometry.measurement.width, top: geometry.measurement.top)
            .background(Colour.grey)
            .gesture(
                DragGesture(minimumDistance: 0)
                    .onChanged { worth in
                        handleDrag(worth.location)
                    }
                    .onEnded { worth in
                        placeTextAlongPath()
                    }
            )
        }
        .background(Colour.white)
        .edgesIgnoringSafeArea(.all)
    }

    // MARK: - Deal with Drag
    non-public func handleDrag(_ place: CGPoint) {
        dragPath.append(place)
    }

    // MARK: - Place Textual content Alongside Path
    non-public func placeTextAlongPath() {
        letters.removeAll()
        
        let pathLength = calculatePathLength()
        
        let numberOfLetters = Int(pathLength / letterSpacing)
        
        for i in 0..<numberOfLetters {
            guard currentIndex < longString.rely else { return }
            let character = Array(longString)[currentIndex]
            currentIndex += 1
            
            let place = calculatePositionAlongPath(atIndex: i)
            let angle = calculatePerpendicularAngle(from: i)
            
            let draggedLetter = DraggedLetter(character: character, place: place, angle: angle)
            letters.append(draggedLetter)
        }
    }

    // MARK: - Calculate Path Size
    non-public func calculatePathLength() -> CGFloat {
        var size: CGFloat = 0
        
        for i in 1..<dragPath.rely {
            let begin = dragPath[i - 1]
            let finish = dragPath[i]
            size += hypot(finish.x - begin.x, finish.y - begin.y)
        }
        
        return size
    }

    // MARK: - Calculate Place Alongside Path
    non-public func calculatePositionAlongPath(atIndex index: Int) -> CGPoint {
        guard dragPath.rely > 1 else { return dragPath.first ?? .zero }
        
        let pathLength = calculatePathLength()
        let segmentLength = CGFloat(index) * letterSpacing
        
        var accumulatedLength: CGFloat = 0
        for i in 1..<dragPath.rely {
            let begin = dragPath[i - 1]
            let finish = dragPath[i]
            let segmentDist = hypot(finish.x - begin.x, finish.y - begin.y)
            accumulatedLength += segmentDist
            
            if accumulatedLength >= segmentLength {
                let ratio = (segmentLength - (accumulatedLength - segmentDist)) / segmentDist
                let x = begin.x + ratio * (finish.x - begin.x)
                let y = begin.y + ratio * (finish.y - begin.y)
                return CGPoint(x: x, y: y)
            }
        }
        
        return dragPath.final ?? .zero
    }

    // MARK: - Calculate Perpendicular Angle (Angle primarily based on drag course)
    non-public func calculatePerpendicularAngle(from index: Int) -> Double {
        guard dragPath.rely > 1 else { return 0 }

        guard dragPath.rely > index else { return 0 }

        let begin = dragPath[index]
        let finish = dragPath[index + 1 < dragPath.count ? index + 1 : index]

        let dx = finish.x - begin.x
        let dy = finish.y - begin.y
        
        let perpDx = -dy
        let perpDy = dx

        let angle = atan2(perpDy, perpDx) * 180 / .pi

        return angle - 90.0
    }
}

// MARK: - Dragged Letter Mannequin
struct DraggedLetter: Identifiable {
    let id = UUID()
    let character: Character
    let place: CGPoint
    let angle: Double
}

The problem:
The textual content is positioned alongside the trail appropriately, however the angle of the textual content doesn’t appear to match the floor of the trail, particularly on sloped or curved paths.
The letters needs to be standing “on the floor” of the trail, whatever the path’s course. The textual content ought to at all times face the “regular” or “perpendicular” course of the trail at every level.

What I’ve tried:
I calculate the angle primarily based on the course of the trail between two factors and use the perpendicular vector to set the angle for every letter.
The letters rotate typically, however on sure slopes or curves, the textual content nonetheless would not align as anticipated.

What I’m searching for:
Assist with guaranteeing the letters at all times face perpendicular to the trail at each level, particularly on sloped or curved paths.
A approach to make sure the letters at all times seem like standing upright, regardless of the form or slope of the drawn path.

what I need you possibly can see on this picture I need textual content keep hooked up at path floor

enter image description here

on plain floor it is little bit okay however whereas in slop or round path it isn’t correct

enter image description here

author avatar
roosho Senior Engineer (Technical Services)
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog. 
rooshohttps://www.roosho.com
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog. 

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here


Latest Articles

author avatar
roosho Senior Engineer (Technical Services)
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog.