1

I'm trying to draw lines with touches and then be able to move it. i didn't use UIContext method with draw(_ rect: CGRect) for drawing because i wasn't able to get size of stroke and some of it's properties, so i used CAShapeLayer for drawing with touches methods like this:

    let shapeLayer: CAShapeLayer = {
    let layer = CAShapeLayer()
    layer.lineWidth = 1
    layer.strokeColor = UIColor.black.cgColor
    layer.fillColor = UIColor.clear.cgColor
    layer.lineCap = .round
    layer.lineJoin = .round
    layer.lineDashPattern = [10, 10]
    layer.name = "ShapeLayer"
    return layer
}()

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let shapeLayer = CAShapeLayer()
        shapeLayer.lineWidth = 1
        shapeLayer.strokeColor = UIColor.black.cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineCap = .round
        shapeLayer.lineJoin = .round
        shapeLayer.lineDashPattern = [10, 10]
        shapeLayer.name = "ShapeLayer"
        self.canvas.layer.addSublayer(shapeLayer)

        path = MyBezierPath()
        if let location = touches.first?.location(in: self.canvas) { previousTouchPoint = location }
}

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    
    guard let touch = touches.first?.location(in: self.canvas) else { return }
    
        if let location = touches.first?.location(in: self.canvas) {
            path.move(to: location)
            path.addLine(to: previousTouchPoint)
            previousTouchPoint = location

            if canvas.layer.sublayers != nil && canvas.layer.sublayers?.last?.name == "ShapeLayer" {
                guard let layer = canvas.layer.sublayers?.last as? CAShapeLayer else { return }
                print("Here \(layer.path?.boundingBoxOfPath)")
                layer.path = path.cgPath
            }
        }
 }

i was trying to add all UIBezierpath to single CAShapeLayer so i can select particular path and move it. But in this method it creates new CAShapeLayer for every line so i tried defining global CAShapeLayer variable and appending UIBezierPath for all lines and add it to global CAShapeLayer variable but it's very slow and laggy. is there any way i can draw with only one CAShapeLayer then be able to change location of it's BezierPath?

Ramesh Sanghar
  • 174
  • 1
  • 1
  • 19
  • 1
    Does this answer your question? [how can i access UIBezierPath that is drawn in draw(\_ rect: CGRect)?](https://stackoverflow.com/questions/73541768/how-can-i-access-uibezierpath-that-is-drawn-in-draw-rect-cgrect) – DonMag Sep 02 '22 at 12:44
  • @DonMag it solves my problem but not answers this question that how to draw multiple lines in single CAShapeLayer, thanks for suggestion. – Ramesh Sanghar Sep 02 '22 at 12:53

1 Answers1

2

Here's a simple example of adding multiple "line segments" to a single UIBezierPath, and then being able to drag/move that path.

In a UIView subclass, we:

  • create a UIBezierPath -- we'll call it thePath
  • create, assign properties, and add a CAShapeLayer -- shapeLayer
  • if we are in "Draw" mode
    • on touchesBegan, thePath.move(to: point)
    • on touchesMoved, thePath.addLine(to: point) and shapeLayer.path = thePath.cgPath
  • if we are in "Move" mode
    • on touchesBegan, save the point
    • on touchesMoved, transform the path

On launch, it looks like this:

enter image description here

we touch-and-drag to add to the path:

enter image description here

a few more touch-and-drags to add more "segments":

enter image description here

now we switch to "Move" and drag down and to the right:

enter image description here

switch back to "Draw" and add a few more segments:

enter image description here

switch back to "Move" and drag up and to the left:

enter image description here

Example controller

class DrawMoveLayerTestVC: UIViewController {

    let testView = DrawMoveLayerView()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemBackground
        
        testView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
        testView.translatesAutoresizingMaskIntoConstraints = false
        
        // segmented control to switch between drawing / moving
        let segControl = UISegmentedControl(items: ["Draw", "Move"])
        segControl.translatesAutoresizingMaskIntoConstraints = false
        segControl.selectedSegmentIndex = 0
        segControl.addTarget(self, action: #selector(segChanged(_:)), for: .valueChanged)
        
        view.addSubview(testView)
        view.addSubview(segControl)
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // put segmented control at bottom
            segControl.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            segControl.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            segControl.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),

            // constrain test view to all top/leading/trailing with 20-points "padding"
            testView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            testView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            testView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            // bottom to segmented control top with 20-points "padding"
            testView.bottomAnchor.constraint(equalTo: segControl.topAnchor, constant: -20.0),
            
        ])
        
    }
    @objc func segChanged(_ sender: UISegmentedControl) {
        // set test view to "drawing" or "moving"
        testView.isDrawing = sender.selectedSegmentIndex == 0
    }
}

Example view subclass

class DrawMoveLayerView: UIView {
    
    public var isDrawing: Bool = true
    
    private let shapeLayer = CAShapeLayer()
    private let thePath = UIBezierPath()
    private var startPoint: CGPoint = .zero

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    private func commonInit() {
        shapeLayer.lineWidth = 1
        shapeLayer.strokeColor = UIColor.black.cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineCap = .round
        shapeLayer.lineJoin = .round
        shapeLayer.lineDashPattern = [5, 10]
        shapeLayer.name = "ShapeLayer"
        layer.addSublayer(shapeLayer)
        self.clipsToBounds = true
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let t = touches.first else { return }
        let point = t.location(in: self)
        if isDrawing {
            thePath.move(to: point)
        } else {
            self.startPoint = point
        }
    }
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let t = touches.first else { return }
        let point = t.location(in: self)
        if isDrawing {
            thePath.addLine(to: point)
        } else {
            // move the path by the distance the touch moved
            let tr = CGAffineTransform(translationX: point.x - startPoint.x, y: point.y - startPoint.y)
            thePath.apply(tr)
            startPoint = point
        }
        // update the path of the shape layer
        shapeLayer.path = thePath.cgPath
    }
    
}
DonMag
  • 69,424
  • 5
  • 50
  • 86
  • @RameshSanghar - when you post an answer that turns out to be incorrect, you can either a) leave it here, if you think it would be beneficial to other users who may come across it, or b) delete it. In this case, I think it makes much more sense to delete your answer. – DonMag Sep 06 '22 at 21:29