Search code examples
iosuiviewcore-graphicscgaffinetransform

How to transform a line during pan gesture event?


I am creating a flow chart and when user drags a node, I want to move the line connected to it. In pan handler event of the node I implemented a CGAffineTransform to transform line, but it does not move line smoothly. Any idea how other flow chart apps handle this problem?

func panHandler(sender: UIPanGestureRecognizer) {

        else if (sender.state == .Changed)
        {
            let translation = sender.translationInView(self)
            sender.view!.center = CGPointMake(originalCenter.x + translation.x, originalCenter.y + translation.y)


           //rv is the connected line, created at an other place like:
           //let w = GraphViewController.calcLength(e.x, y0: e.y, x1: e2.x, y1: e2.y)
           //rv = UIView(frame: CGRectMake(0, 0, CGFloat(w), 1.0))
           //var angle = GraphViewController.calcAngle(e.x, y0: e.y, x1: e2.x, y1: e2.y)
           //rv.transform = CGAffineTransformMakeRotation(CGFloat(angle))

            var angle = GraphViewController.calcAngle(e!.x, y0: e!.y, x1: r.destination!.x, y1:r.destination!.y)
            rv!.transform = CGAffineTransformMakeRotation(CGFloat(angle))

        }
    }

Solution

  • Because you don't need any touch events on the line, we could use a plain CAShapeLayer and update its path on pan. I assume you want to have some anchor point for the line.

    Define some anchor point for your line

    endOfLineAnchor = CGPoint(x: view.bounds.width / 2, y: view.bounds.height * 0.8)
    

    Create some view to attach it to

    ballView = createBallView()
    ballView.center = CGPoint(x: view.bounds.width / 2, y: view.bounds.height / 2)
    

    Add layer & view

        lineLayer = CAShapeLayer()
        lineLayer.strokeColor = UIColor.blackColor().CGColor
        lineLayer.path = pathFromBallToAnchor()
        view.layer.addSublayer(lineLayer)
        view.addSubview(ballView)
    

    On pan update the layer's path

        else if (sender.state == .Changed)
        {
            let translation = sender.translationInView(self.view)
            sender.view!.center = CGPointMake(originalCenter.x + translation.x, originalCenter.y + translation.y)
            CATransaction.begin()
            CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
            lineLayer.path = pathFromBallToAnchor()
            CATransaction.commit()
        }
    

    the reason for the CATransaction is that we want to disable implicit animations.

    Construct the path:

        func pathFromBallToAnchor() -> CGPathRef {
            var bazier = UIBezierPath()
            bazier.moveToPoint(ballView.center)
            bazier.addLineToPoint(endOfLineAnchor)
            return bazier.CGPath
        }
    

    the result:

    Panned Ball attached to an anchored line in iPhone 5 iOS 8.1 simulator