0

I'm encountering an issue with my drawing functionality in Swift, specifically when using CALayer instead of UIBezierPath. Here's the problem I'm facing:

I am creating a texture from an image and using the texture to create a drawing line through finger stroke. In this process, the line is being created by joining the texture one by one, side by side. Initially, I used UIBezierPath for drawing, and everything worked fine. However, when I switched to using CALayer for drawing, for using textures to draw lines, I noticed that the touch points are not sequentially joining or increasing if I move my finger fast. drawing speed

When I move my finger slowly, the drawing works fine, and the touch points are joining sequentially. However, when I move my finger fast, the touch points seem to skip, resulting in disjointed lines and multiple blank spaces within a line.

class CustomStrokeDrawingView: UIView {
    var path = UIBezierPath()
    var startPoint = CGPoint()
    var touchPoint = CGPoint()
    var shape =  UIImage(named:"square-outline")

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        if let point = touch?.location(in: self) {
            startPoint = point
        }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        if let point = touch?.location(in: self) {
            touchPoint = point
        }

        let imageLayer = CALayer()
        imageLayer.backgroundColor = UIColor.clear.cgColor
        imageLayer.bounds = CGRect(x: startPoint.x, y: startPoint.y , width: 20, height: 20)
        imageLayer.position = CGPoint(x:touchPoint.x ,y:touchPoint.y)
        imageLayer.contents = shape?.cgImage
        self.layer.addSublayer(imageLayer)
        
        
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
    }
}
1
  • "when I move my finger fast, the touch points seem to skip" -- yes, you can move your finger much faster than iOS can trigger the touchesMoved event. If you want a "filled in line" - with multiple copies of your image - you will need a different approach. Commented Feb 7, 2024 at 16:15

1 Answer 1

1

It is very easy to move your finger faster than iOS can generate touchesMoved events ... if you log the points with print() statements, you can easily see this in the debug console. It's possible to "swipe" fast enough across the device screen and only generate 2 or 3 points.

One approach is to interpolate n-number of points on the lines between the touchesMoved points, and then draw your shape image at each of those points.

Here's some quick example code...


UIView subclass

class CustomStrokeDrawingView: UIView {
    
    var shape: UIImage!
    var pts: [CGPoint] = []

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        // crash if we can't load the "brush" image
        guard let img =  UIImage(named:"square-outline") else {
            fatalError("Could not load shape image!")
        }
        shape = img
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        if let startPoint = touch?.location(in: self) {
            // reset points array to only the starting point
            //print("Start:", startPoint)
            pts = [startPoint]
        }
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        if let touchPoint = touch?.location(in: self) {
            //print("Moved:", touchPoint)
            pts.append(touchPoint)
            // trigger draw()
            setNeedsDisplay()
        }
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        guard pts.count > 1 else { return }

        let shapeSize: CGSize = shape.size
        
        // adjust as desired
        let pixelsPerIteration: CGFloat = 5.0

        for idx in 0..<(pts.count - 1) {
            
            let startx: CGFloat = pts[idx].x
            let starty: CGFloat = pts[idx].y
            let dx: CGFloat = pts[idx+1].x - startx
            let dy: CGFloat = pts[idx+1].y - starty
            
            let distance: CGFloat = sqrt(dx * dx + dy * dy)
            let iterations = distance / pixelsPerIteration + 1
            let dxIncrement = dx / iterations
            let dyIncrement = dy / iterations
            
            var dstRect: CGRect = .init(x: 0.0, y: 0.0, width: shapeSize.width, height: shapeSize.height)
            
            var x: CGFloat = startx - shapeSize.width / 2.0
            var y: CGFloat = starty - shapeSize.height / 2.0
            
            // draw a series of shape images to form the line
            for _ in 0..<Int(iterations) {
                dstRect.origin.x = x
                dstRect.origin.y = y
                x += dxIncrement
                y += dyIncrement
                shape.draw(in: dstRect)
            }
        }
    }
}

Example controller class

class DrawTestVC: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        navigationController?.setNavigationBarHidden(true, animated: false)
        
        view.backgroundColor = .systemYellow
        
        let drawView = CustomStrokeDrawingView()
        drawView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(drawView)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            drawView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            drawView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            drawView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            drawView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
        ])
        
        drawView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
    }
    
}

Looks about like this:

enter image description here

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you so much for your help! Your solution resolved my issue perfectly. I've marked it as the accepted answer and upvoted it. I really appreciate the time you took to assist me. I'll be sure to keep an eye on this thread and update it if I encounter any related problems in the future. Thanks again!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.