0

How to draw a BezierPath on a button tap?

I'm practicing with BezierPaths. What I'm trying to do is draw a BezierPaths shape when a button is tapped but I'm not seeing anything on the screen when I tap the button. I'm not getting any errors just the output in the debug area shown below.

What am I missing?

Here is my code:

@IBAction func drawIt(_ sender: Any) {
    let bezierPath = UIBezierPath()
    bezierPath.move(to: CGPoint(x: 46.5, y: 50.14))
    bezierPath.addCurve(to: CGPoint(x: 77.5, y: 50.5), controlPoint1: CGPoint(x: 46.5, y: 50.14), controlPoint2: CGPoint(x: 64.74, y: 34.09))
    bezierPath.addCurve(to: CGPoint(x: 94.5, y: 96.5), controlPoint1: CGPoint(x: 84.5, y: 59.5), controlPoint2: CGPoint(x: 88.29, y: 74.25))
    UIColor.black.setStroke()
    bezierPath.lineWidth = 1
    bezierPath.stroke()
}

Debug Area:

CGContextSetStrokeColorWithColor: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.

fs_tigre
  • 10,650
  • 13
  • 73
  • 146

1 Answers1

1

Everything was okay until you said:

bezierPath.stroke()

Up until then, you were just making a path, which is a neutral set of instructions. But when you said stroke(), you were saying: "Okay, now draw it, baby!" To which the runtime is now replying: "Uh, where? You can only draw in some kind of drawing (graphics) context, such as a UIView's graphics context, a CALayer's graphics context, or a UIImage context. You aren't in any of those. You're trying to draw in the middle of nowhere. I can't do that, and even if I could, you wouldn't see anything."

So, what's a good way to play with drawing commands? If you want to draw in response to a button click, here's the best approach:

  1. Make a UIGraphicsImageRenderer of appropriate size.

  2. Call the renderer's image method.

  3. The image method takes a closure. That's where you draw!

  4. You now have a UIImage (returned from the call to the image method).

  5. Make a UIImageView and set its image to that image.

  6. Stick the UIImageView into your interface.

Example:

@IBAction func drawIt(_ sender: Any) {
    let r = UIGraphicsImageRenderer(size: CGSize(width: 150, height: 150))
    let im = r.image { _ in
        let bezierPath = UIBezierPath()
        bezierPath.move(to: CGPoint(x: 46.5, y: 50.14))
        bezierPath.addCurve(to: CGPoint(x: 77.5, y: 50.5), controlPoint1: CGPoint(x: 46.5, y: 50.14), controlPoint2: CGPoint(x: 64.74, y: 34.09))
        bezierPath.addCurve(to: CGPoint(x: 94.5, y: 96.5), controlPoint1: CGPoint(x: 84.5, y: 59.5), controlPoint2: CGPoint(x: 88.29, y: 74.25))
        UIColor.black.setStroke()
        bezierPath.lineWidth = 1
        bezierPath.stroke()
    }
    let iv = UIImageView(image:im)
    iv.frame.origin = CGPoint(x: 100, y: 100)
    iv.layer.borderWidth = 1
    self.view.addSubview(iv)
}
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 1
    This is that same paper toss parabola you're trying to make, isn't it? :) – matt Nov 25 '20 at 18:40
  • Yes, that is exactly what I'm drawing. I got the animation working using `animateKeyframes` but I for some reason want to explore animating using BezierPaths but I'm finding them a lot more complex than what I expected. Thanks a lot for your help. – fs_tigre Nov 25 '20 at 20:27
  • 1
    You saw this, right? https://stackoverflow.com/questions/38723031/undestanding-uibezierpath-curving-mechanism-controlpoint-and-the-curve-point – matt Nov 25 '20 at 21:45