Search code examples
swiftuibezierpath

UIBezierPath line disappears when drawing another one


I'm trying to draw multiple with UIbezierPath but whenever I try to draw the 2nd one, the first one disappears. Tried to create multiple BezierPath and put them into an array but I got this error when it run into case:ended

test[2687:238999] [Unknown process name] CGPathCloseSubpath: no current point.

This is my code


 private lazy var lineShape: CAShapeLayer = {
        let lineShape = CAShapeLayer()
        lineShape.strokeColor = UIColor.blue.cgColor
        lineShape.lineWidth = 2.0

        return lineShape
    }()
    
 var bezierPathArr:NSMutableArray = []
 private var panGestureStartPoint: CGPoint = .zero
 private lazy var panRecognizer: UIPanGestureRecognizer = {
        return UIPanGestureRecognizer(target: self, action: #selector(panGestureCalled(_:)))
    }()

 @objc func panGestureCalled(_: UIPanGestureRecognizer) {
        let linePath = UIBezierPath()
        let currentPanPoint = panRecognizer.location(in: self.view)
        switch panRecognizer.state {
        case .began:
            panGestureStartPoint = currentPanPoint
            self.view.layer.addSublayer(lineShape)
            bezierPathArr.add(linePath)

        case .changed:
            linePath.move(to: panGestureStartPoint)
            linePath.addLine(to: currentPanPoint)
            lineShape.path = linePath.cgPath

        case .ended:
            let finalPath:UIBezierPath = bezierPathArr.lastObject as! UIBezierPath
            finalPath.close()
    
        default: break
        }
    }



Solution

  • Because of the lazy creation of your CAShapeLayer, whenever you are adding a layer to your view, you are using the same object reference. That along with setting the path on that reference would give you the behavior you are experiencing.

    In order to achieve the behavior expected, you need to create and store a new CAShapeLayer for each of your completed gestures. Here's a working example:

    class ViewController: UIViewController {
        
        private lazy var panRecognizer: UIPanGestureRecognizer = {
            return UIPanGestureRecognizer(target: self, action: #selector(panGestureCalled(_:)))
        }()
        
        /// The path that is being drawn (resets on .began, closes on .ended)
        private var currentPath: UIBezierPath = .init()
        /// All of the layers added to the view
        private var shapes: [CAShapeLayer] = []
        
        override func viewDidLoad() {
            super.viewDidLoad()
            view.addGestureRecognizer(panRecognizer)
        }
    
        @objc private func panGestureCalled(_ sender: UIPanGestureRecognizer) {
            let point = sender.location(in: self.view)
            switch sender.state {
            case .began:
                // Reset the path
                currentPath = .init()
                // Set the starting point
                currentPath.move(to: point)
                // Creates a new layer when gesture starts.
                let layer = newShapeLayer()
                // Save the layer with all shapes.
                shapes.append(layer)
                // Add the layer to the view
                view.layer.addSublayer(layer)
            case .changed:
                // Update the current path to the new point
                currentPath.addLine(to: point)
            case .ended:
                // Close/Finish the path
                currentPath.close()
            default:
                break
            }
            
            // Update the most-current shape with the path being drawn.
            shapes.last?.path = currentPath.cgPath
        }
        
        /// Creates a new `CAShapeLayer`
        private func newShapeLayer() -> CAShapeLayer {
            let layer = CAShapeLayer()
            layer.frame = view.bounds
            layer.strokeColor = UIColor.blue.cgColor
            layer.lineWidth = 2.0
            return layer
        }
    }