12

How can I update position of an already drawn CGPath/UIBezierPath on a view? I would like to move or change a path's position and then perhaps call the drawInRect method to just render the path again.

Eugene Berdnikov
  • 2,150
  • 2
  • 23
  • 30
SleepNot
  • 2,982
  • 10
  • 43
  • 72

3 Answers3

25

From your question it sounds like you are drawing the path using Core Graphics inside drawRect: (as compared to using a CAShapeLayer) so I'll explain the version first.

Moving a CGPath

You can create a new path by transforming another path. A translation transform moves the transformed object a certain distance in x and y. So using a translation transform you can move your existing path a certain number of points in both x and y.

CGAffineTransform translation = CGAffineTransformMakeTranslation(xPixelsToMove,
                                                                 yPixelsToMove);
CGPathRef movedPath = CGPathCreateCopyByTransformingPath(originalCGPath,
                                                         &translation);

Then you could use the movedPath to draw the same way you are already doing.

You could also change modify the same path

yourPath = CGPathCreateCopyByTransformingPath(yourPath,
                                              &translation);

and simply redraw it.

Moving a shape layer

If you are using a shape layer, moving it is even easier. Then you only have to change the position of the layer using the position property.

Update:

If you want to use a shape layer you can simply create a new CAShapeLayer and set its path to be your CGPath. You will need QuartzCore.framework for this since CAShapeLayer is part of Core Animation.

CAShapeLayer *shape = [CAShapeLayer layer];
shape.path = yourCGParth;
shape.fillColor = [UIColor redColor].CGColor;

[someView.layer addSublayer:shape];

Then to move the shape you simply change its position.

shape.position = thePointYouWantToMoveTheShapeTo;
Community
  • 1
  • 1
David Rönnqvist
  • 56,267
  • 18
  • 167
  • 205
  • I tried the first code you suggested CGAffineTransformMakeTranslation but with the current x and y of my touch as the parameters. It doesnt seem to be following the touch location and is kinda incrementing the position of the path. – SleepNot Jun 19 '13 at 03:34
  • @jeraldov You should pass in the difference in x and y. For example the `translationInView:` for a pan gesture recognizer. That said, if you are going to move the shape a lot you should consider a shape layer. – David Rönnqvist Jun 19 '13 at 05:19
  • Yes I'll be moving the shape a lot since I want to drag/move a shape on TouchesMoved event. I will try to go with the shape layer route. Can you give me a sample or a tutorial on how to do shape layers? Thanks. – SleepNot Jun 19 '13 at 06:17
  • @jeraldov I've updated my question. Does it explain enough about shape layers? – David Rönnqvist Jun 19 '13 at 07:24
  • Yes it does. I am now able to reposition my drawings. – SleepNot Jun 20 '13 at 03:29
  • Hi. I run into some problem again, it seems like if I change the position of the shape, it doesnt appear on the view. How do I update the position of the view and make the change visible. – SleepNot Jun 21 '13 at 03:34
  • Hello @DavidRönnqvist, can this logic be used for undo/redo like when we do undo, move the path out of the screen and when we do redo bring back to its initial position. Not sure just a thought – Ranjit Feb 01 '14 at 13:07
  • @Ranjit in theory it could but creating a new copy of the path and just moving it off screen for every step that can be undone/redone could introduce performance problems, so it's best to prototype and measure first. – David Rönnqvist Feb 01 '14 at 13:24
  • ok @DavidRönnqvist, could be please look at this http://stackoverflow.com/questions/21438586/undo-redo-for-drawing-in-ios, I am totally stuck with it – Ranjit Feb 01 '14 at 13:26
  • could you show your implementation in `Swift` too? I am confused about `&translation` expression – János Oct 26 '14 at 00:03
  • The translated path has incorrect bounds, lets say I moved a layer from point(0.0) to point(100, 100), but when I print the boundingbox of cgpath its some where around point(150, 200). any idea whats the issue. – Raj Kiran Sep 01 '21 at 23:16
7

/// Translate cgPath from its center to give point.No need to move shape layer .Moving path will make app smooth

func translate(path : CGPath?, by point: CGPoint) -> CGPath? {

    let bezeirPath = UIBezierPath()
    guard let prevPath = path else {
        return nil
    }
    bezeirPath.cgPath = prevPath
    bezeirPath.apply(CGAffineTransform(translationX: point.x, y: point.y))

    return bezeirPath.cgPath
}
Arnav
  • 668
  • 6
  • 14
  • The translated path has incorrect bounds, lets say I moved a layer from point(0.0) to point(100, 100), but when I print the boundingbox of cgpath its some where around point(150, 200). any idea whats the issue. – Raj Kiran Sep 01 '21 at 23:16
0

I did it a bit different from other answers above.

Let's suppose you want to move a circle, first I define its center coordinates and radius in global variables, like this:

var center: CGPoint = CGPoint(x: UIScreen.main.bounds.width/2, y: UIScreen.main.bounds.height/2)
var radius: CGFloat = 10

I draw this circle like in the code bellow:

override func draw(_ rect: CGRect) {
    super.draw(rect)  
    drawCircle()
}

func drawCircle() {
    let circle = UIBezierPath(
        arcCenter: self.center,
        radius: CGFloat(self.radius),
        startAngle: CGFloat(0),
        endAngle: CGFloat(2*Double.pi),
        clockwise: true)

    let circleLayer = CAShapeLayer()
        circleLayer.path = circle.cgPath
        circleLayer.fillColor = UIColor.clear.cgColor
        circleLayer.strokeColor = UIColor.black.cgColor
        circleLayer.lineWidth = 10.0
        layer.addSublayer(circleLayer)
}

Inside the UIView, I have a touchesMoved(), like this:

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?){
    if let touch = touches.first {
        let pos = touch.location(in: self)

        if (pow(pos.x-self.center.x,2) + pow(pos.y-self.center.y,2) <= pow(self.radius,2)){
                self.center = CGPoint(x: pos.x, y: pos.y)
        }

        setNeedsDisplay()
    }
}

Everytime the touch is inside the circle, its center coordinates are updated and the UIView is redrawn with setNeedDisplay(), so the circle moves.

Hope it helps someone!

zampnrs
  • 345
  • 1
  • 2
  • 13