Search code examples
swiftcore-graphics

Get Core Graphics context of programatically created view


Summary: Get Core Graphics context of programatically created view, inline, and draw into it, or is that just not how you do it?

I'm creating a view programmatically, obtaining a bezierpath from a function, and then I was hoping to draw that path into the view.

I understand that to use Core Graphics I need a Core Graphics context to draw into, and that for a UIView that 'comes for free' in the draw method - the context can be referenced with UIGraphicsGetCurrentContext().

I also realise I can create a custom class that inherits from UIView and override the draw method to do my drawing there.

I've also seen this pattern

let image = renderer.image { (context) in
    // draw stuff
}

But that seems to imply a UIImage is always returned? Is that just the case? Is that what the draw method in a UIView is doing anyway? Ultimately returning an image?

Can I not do it 'inline'? Hopefully this code snippet demonstrates what I was expecting to be able to do. If I can do it, how? If I can't, why? What am I missing in my understanding of Swift and the Core Graphics framework? I'm new to both. Is what I'm trying to do just not very "Swifty"? (I come from JavaScript, it's the Wild West out there!)

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let path = getPath() // returns UIBezierPath
        let myView = UIView()
        myView.frame = CGRect(x: 96, y: 48, width: 64, height: 256)
        // myView. get it's context ???
        // myView. draw path ito it ???
        view.addSubview(myView)
       
    }
    
}

Solution

  • Think of the path as the model that your view is displaying. Setting this data can be done in advance, then the drawing can be done when it's needed. Views draw themselves when the system asks them to.

    You could have a custom UIView subclass, which has a UIBezierPath property:

    class MyView: UIView {
      var path: UIBezierPath?
      override func draw(_ rect: CGRect) {
        // When the system calls this function there is an active graphics context
        path.stroke() // plus whatever else you want to do
      }
    }
    

    You'd create the view and set the path in viewDidLoad, but would leave the view to draw itself when the system asks it to.

    The drawing commands you do in this method are composited together with other views during rendering, to make the final bitmap for the affected area of the screen. It's not quite the same as returning an image.

    If you really wanted to "draw" the thing up front, once only, then you would use the UIGraphicsImageRenderer, which does give you an image, and pass that to a UIImageView, which is a UIView subclass that draws images.