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?
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:
UIBezierPath
-- we'll call it thePath
CAShapeLayer
-- shapeLayer
thePath.move(to: point)
thePath.addLine(to: point)
and shapeLayer.path = thePath.cgPath
On launch, it looks like this:
we touch-and-drag to add to the path:
a few more touch-and-drags to add more "segments":
now we switch to "Move" and drag down and to the right:
switch back to "Draw" and add a few more segments:
switch back to "Move" and drag up and to the left:
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
}
}