Featured Image

This is Why You Should Draw Images with UIGraphicsImageRenderer

Introduction

There are usually many ways to solve the same problem, but in fact, people always prefer simpler, easier and faster solutions. Programming is no exception. Core Graphics framework provides low-level, high-fidelity, and lightweight 2D rendering. However, developers must deal with configurations, such as color depth and image scale, or manage the Core Graphics context, which makes drawing images complicated. Fortunately, starting from iOS 10, UIKit provides a new API, which is is UIGraphicsImageRenderer, to simplify the drawing process with many productive features, such as sensible default values ​​for current devices, safe UIImage object return, and dedicated Core Graphics context for each renderer.

Sensible default values for current devices

Using appropriate values ​​to create a Core Graphics context on the user’s device is the first problem that needs to be solved. Developers have to handle configuration such as color depth and image scale with UIGraphicsBeginImageContextWithOptions before iOS 10 or without UIGraphicsImageRenderer. Alternatively, UIGraphicsImageRenderer can select sensible default values for the user’s device itself at runtime.

// Create a renderer with sensible default values for current devices
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 640, height: 360))

Optionally, developers specify nondefault parameters the renderer should use to create its context with a UIGraphicsImageRendererFormat object.

// Create a nondefault parameters
let fmt = UIGraphicsImageRendererFormat()
fmt.opaque = true
fmt.scale = UIScreen.main.scale
fmt.preferredRange = .standard
// Specify the nondefault parameters to the renderer        
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 640, height: 360), format: fmt)

Therefore, UIGraphicsImageRenderer improves the work efficiency of developers in the configuration of the Core Graphics context.

Safe UIImage object return

Unlike UIGraphicsGetImageFromCurrentImageContext that returns an optional UIImage, UIGraphicsImageRenderer returns a UIImage object, thereby reducing complexity and code in judging whether the result is nil.

let image = renderer.image { (context) in
  UIColor.darkGray.setStroke()
  context.stroke(renderer.format.bounds)
  UIColor(colorLiteralRed: 158/255, green: 215/255, blue: 245/255, alpha: 1).setFill()
  context.fill(CGRect(x: 1, y: 1, width: 140, height: 140))
}
// image is always available here
let size = image.size

Dedicated Core Graphics context

The renderer creates a dedicated Core Graphics context using the parameters provided when the renderer was initialized. And developers can access the context in the closure of the image(actions:). A dedicated Core Graphics context in UIGraphicsImageRenderer takes two advantages. Firstly, the lifecycle of the context is the same as the renderer, so that developers do not need to release the context manually. Secondly, developers do not need to switch between multiple accounts manually.

import UIKit
import PlaygroundSupport

class MyViewController : UIViewController {
    override func loadView() {
        let view = UIView()
        view.backgroundColor = .white

        // Canvas size
        let size = CGSize(width: 200, height: 200)
        
        // Red circle
        let circle = UIGraphicsImageRenderer(size: size).image { ctx in
            UIColor.red.setFill()
            ctx.cgContext.addEllipse(in: CGRect(origin: .zero, size: size))
            ctx.cgContext.fillPath()
        }
        
        // Green rectangle
        let rectangle = UIGraphicsImageRenderer(size: size).image { ctx in
            UIColor.green.setFill()
            ctx.fill(CGRect(origin: .zero, size: size))
        }
        
        // Final image
        let image = UIGraphicsImageRenderer(size: size).image { ctx in
            rectangle.draw(at: .zero)
            circle.draw(at: .zero)
        }
        
        let imageView = UIImageView(image: image)
        imageView.frame = CGRect(origin: .zero, size: size)
        
        view.addSubview(imageView)
        self.view = view
    }
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
Playground snapshot

Conclusion

As stated previously, we have discussed how UIGraphicsImageRenderer reduces complexity and code in terms of initialization context, safe return value, and multi-context management when developers draw images. Therefore, it is time to use UIGraphicsImageRenderer to draw images in your iOS apps. 

Leave a Reply

Your email address will not be published. Required fields are marked *