I am trying to create some draggable UIViews that are connected by lines. See image below:
I can create the draggable circles by creating a class that is a subclass of UIView and overriding the draw function
override func draw(_ rect: CGRect) {
let path = UIBezierPath(ovalIn: rect)
let circleColor:UIColor
switch group {
case .forehead:
circleColor = UIColor.red
case .crowsFeetRightEye:
circleColor = UIColor.green
case .crowsFeetLeftEye:
circleColor = UIColor.blue
}
circleColor.setFill()
path.fill()
}
and then add a pan gesture recognizer for the dragging
func initGestureRecognizers() {
let panGR = UIPanGestureRecognizer(target: self, action: #selector(DragPoint.didPan(panGR:)))
addGestureRecognizer(panGR)
}
@objc func didPan(panGR: UIPanGestureRecognizer) {
if panGR.state == .changed {
self.superview!.bringSubview(toFront: self)
let translation = panGR.translation(in: self)
self.center.x += translation.x
self.center.y += translation.y
panGR.setTranslation(CGPoint.zero, in: self)
}
}
However, I am totally stuck on how to go about the connecting lines and parenting the start/end points to its corresponding circle when dragged. Is there anybody who can help or point me in the right direction please?
You want to use CAShapeLayers
with UIBezier
paths to draw the lines between the circles and then change the paths when the user moves the views.
Here is a playground showing an implementation. You can copy and paste this into a playground to see it in action.
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class CircleView : UIView {
var outGoingLine : CAShapeLayer?
var inComingLine : CAShapeLayer?
var inComingCircle : CircleView?
var outGoingCircle : CircleView?
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.cornerRadius = self.frame.size.width / 2
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func lineTo(circle: CircleView) -> CAShapeLayer {
let path = UIBezierPath()
path.move(to: self.center)
path.addLine(to: circle.center)
let line = CAShapeLayer()
line.path = path.cgPath
line.lineWidth = 5
line.strokeColor = UIColor.red.cgColor
circle.inComingLine = line
outGoingLine = line
outGoingCircle = circle
circle.inComingCircle = self
return line
}
}
class MyViewController : UIViewController {
let circle1 = CircleView(frame: CGRect(x: 100, y: 100, width: 50, height: 50))
let circle2 = CircleView(frame: CGRect(x: 100, y: 200, width: 50, height: 50))
let circle3 = CircleView(frame: CGRect(x: 100, y: 300, width: 50, height: 50))
let circle4 = CircleView(frame: CGRect(x: 100, y: 400, width: 50, height: 50))
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
circle1.backgroundColor = .red
view.addSubview(circle1)
circle2.backgroundColor = .red
view.addSubview(circle2)
circle3.backgroundColor = .red
view.addSubview(circle3)
circle4.backgroundColor = .red
view.addSubview(circle4)
circle1.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:))))
circle2.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:))))
circle3.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:))))
circle4.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:))))
view.layer.addSublayer(circle1.lineTo(circle: circle2))
view.layer.addSublayer(circle2.lineTo(circle: circle3))
view.layer.addSublayer(circle3.lineTo(circle: circle4))
}
@objc func didPan(gesture: UIPanGestureRecognizer) {
guard let circle = gesture.view as? CircleView else {
return
}
if (gesture.state == .began) {
circle.center = gesture.location(in: self.view)
}
let newCenter: CGPoint = gesture.location(in: self.view)
let dX = newCenter.x - circle.center.x
let dY = newCenter.y - circle.center.y
circle.center = CGPoint(x: circle.center.x + dX, y: circle.center.y + dY)
if let outGoingCircle = circle.outGoingCircle, let line = circle.outGoingLine, let path = circle.outGoingLine?.path {
let newPath = UIBezierPath(cgPath: path)
newPath.removeAllPoints()
newPath.move(to: circle.center)
newPath.addLine(to: outGoingCircle.center)
line.path = newPath.cgPath
}
if let inComingCircle = circle.inComingCircle, let line = circle.inComingLine, let path = circle.inComingLine?.path {
let newPath = UIBezierPath(cgPath: path)
newPath.removeAllPoints()
newPath.move(to: inComingCircle.center)
newPath.addLine(to: circle.center)
line.path = newPath.cgPath
}
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
PlaygroundPage.current.needsIndefiniteExecution = true
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With