2

I am trying to make an app that draws an object for the user. I now have one of those object, which is built up of an array of type [UIBezierPath]. I then use a loop to change all the UIBezierPaths in the array into CGPaths and then want to animate those paths being drawn one by one. However when I try this code now it doesn't work, and I can't really find any helpful information about this online. This is the code is use to transform the array macbookPath which consists of UIBezierPath into CGPath:

for path in macbookPath {
        drawingPath.append(path.cgPath)
    }

And then I use this code to try and draw the path:

for cgPath in drawingPath {
        shapeLayer.path = cgPath
    }

This is the rest of the code for the function drawForm() which is supposed to draw the form onto a view called aiDrawView:

@objc func drawForm() {
    var drawingPath = [CGPath]()
    var macbookPath = Forms.MacbookForm()
    let shapeLayer = CAShapeLayer()
    shapeLayer.frame = aiDrawView.bounds

    for path in macbookPath {
        drawingPath.append(path.cgPath)
    }

    for cgPath in drawingPath {
        shapeLayer.path = cgPath
    }

    let strokeEndAnimation = CABasicAnimation(keyPath: "strokeEnd")
    strokeEndAnimation.duration = 2.0
    strokeEndAnimation.fromValue = 0.0

    shapeLayer.add(strokeEndAnimation, forKey: "strokeEndAnimation")
}

I am very new to CAShapeLayer and CABasicAnimation and UIBezierPath so any help would be tremendously appreciated!!! :D

Rob
  • 415,655
  • 72
  • 787
  • 1,044
Thijs van der Heijden
  • 1,147
  • 1
  • 10
  • 25
  • What effect are you after? Do you want to draw path one, starting from the beginning point, making the path longer and longer until the full path is drawn, and then drawing the new path on top in the same beginning-to-end fashion, until all the paths in your array are visible at once? Do you want to trace one path beginning to end, then erase it and draw the 2'nd path beginning-to-end? – Duncan C Mar 26 '18 at 20:00
  • Your current code doesn't make sense for a couple of reasons. 1: Your for loop will replace your shape layer's path with each path in your array until the shape layer's path contains the last path, and you won't do any drawing with anything but the last path. 2: You create a CAAnimation that sends the fromValue to 0, but you never set the `toValue` to 1. That **should** work, but it would be safer to explicitly set the `toValue` to 1.0 – Duncan C Mar 26 '18 at 20:05

1 Answers1

9

I'd suggest building a single UIBezierPath from your array of paths and then animate that:

var paths: [UIBezierPath] = ...

let path = UIBezierPath()

paths.forEach { path.append($0) }

shapeLayer.path = path.cgPath

let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = 2.0
animation.fromValue = 0.0
shapeLayer.add(animation, forKey: nil)

E.g., here are 100 paths, combined into one path, whose strokeEnd is then animated on a shape layer:

enter image description here

Assuming your array of paths really form some continuous path, I might suggest storing it as an array of CGPoint instead, though. Then you can build a single path from that. That eliminates the overhead of all of this path stuff. Plus it opens up door for scaling of the original dataset without introduction of any artifacts, being able to use line join options, applying smoothing algorithms if you have undesirable sequence of line segments, etc.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Cool illustration. (Voted.) How did you create 100 sub-paths? Generate them using a `sin()` function? And then how did you create the GIF animation from a CAAnimation? – Duncan C Mar 26 '18 at 20:08
  • What would happen if the paths you were assembling were discontinuous? Would it the stroke animation have discontinuities and jump from the end of one sub-path to the beginning of the next in an instant? That's what I would expect. – Duncan C Mar 26 '18 at 20:10
  • Yep, just [sine curve with `sin` function](https://gist.github.com/robertmryan/c79bb3b236192a7b22692cc64779dbb1). Yep, [discontinuous path works fine](https://imgur.com/a/ZZWyS), too, tho I believe it jumps instantaneously. Re GIF animation, it's just a QuickTime screen capture of the simulator, converted to GIF in Photoshop. – Rob Mar 26 '18 at 20:16
  • Thanks dude! That actually helped me a lot! I do have another question though, because the path now appears in the top left of my screen while I want it to appear in the center. Scaling the path was simple using the .transform method, but moving the path is a lot harder, and doesn't work.... – Thijs van der Heijden Mar 26 '18 at 22:49
  • Your "discontinuous" path looks like it's actually a normal path with dashed line. That's not really a discontinuous path. – Duncan C Mar 26 '18 at 23:59
  • Lol. It may bear some superficial resemblance to a continuous path with a dashed pattern, but that’s not what it was. It was a line path from point 0 to point 3, another from point 5 to point 8, another from point 10 to point 13, etc. And all of those separated line paths were appended to a single `UIBezierPath` and stroked with the standard solid stroke. I was merely demonstrating to my own satisfaction that discontinuous `UIBezierPath` works precisely as anticipated. You don’t have to believe me if you don’t want to. ;) – Rob Mar 27 '18 at 00:28
  • I believe you! If your output was for public consumption, though, you might have chosen a more obviously discontinuous path. – Duncan C Mar 27 '18 at 13:12