The Joys of iOS 10 UIKit Drawing

I just spent a few days enjoying all the iOS 10 UIGraphics renderer utilities for images and PDFs and wide colors and so forth. It’s lovely. I thought I’d share a post comparing the old world and the new.

Old

Remember this?

// Create a color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 
if (colorSpace == NULL) {
    NSLog(@"Error allocating color space");
    return nil; 
}

// Create the bitmap context.
CGContextRef context = CGBitmapContextCreate(
    NULL, width, height,
    BITS_PER_COMPONENT, // bits = 8 per component 
    width * ARGB_COUNT, // 4 bytes for ARGB 
    colorSpace,
    (CGBitmapInfo) kCGImageAlphaPremultipliedFirst); 

if (context == NULL) {
    NSLog(@"Error: Context not created!"); 
    CGColorSpaceRelease(colorSpace ); 
    return nil;
}

// Push the context.
UIGraphicsPushContext(context);

// Perform drawing here

UIGraphicsPopContext();

// Convert to image
CGImageRef imageRef = CGBitmapContextCreateImage(context); 
UIImage *image = [UIImage imageWithCGImage:imageRef];

// Clean up 
CGColorSpaceRelease(colorSpace ); 
CGContextRelease(context); 
CFRelease(imageRef);

New

let image = renderer.image { context in
    let bounds = context.format.bounds
    for amount in stride(from: 1.0 as CGFloat, to: 0.0, by: -0.1) {
        let color = UIColor(hue: amount, saturation: 1.0, 
            brightness: 1.0, alpha: 1.0)
        let rects = bounds.divided(
            atDistance: amount * bounds.size.width, from: .maxXEdge)
        color.set(); UIRectFill(rects.0)
    }
}

and

public func imageExample(size: CGSize) -> UIImage? {
    let bounds = CGRect(origin: .zero, size: size)
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let (width, height) = (Int(size.width), Int(size.height))
    
    // Build Core Graphics ARGB context
    guard let context = CGContext(data: nil, width: width, 
        height: height, bitsPerComponent: 8, 
        bytesPerRow: width * 4, space: colorSpace, 
        bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue) 
        else { return nil }
    
    // Prepare CG Context for UIKit
    UIGraphicsPushContext(context); defer { UIGraphicsPopContext() }
    
    // Draw to context using UIKit calls
    UIColor.blue.set(); UIRectFill(bounds)
    let oval = UIBezierPath(ovalIn: bounds)
    UIColor.red.set(); oval.fill()
    
    // Fetch the image from the context
    guard let imageRef = context.makeImage() else { return nil }
    return UIImage(cgImage: imageRef)
}

and

extension UIImage {
    public func grayscaled() -> UIImage? {
        guard let cgImage = cgImage else { return nil }
        let colorSpace = CGColorSpaceCreateDeviceGray()
        let (width, height) = (Int(size.width), Int(size.height))
        
        // Build context: one byte per pixel, no alpha
        guard let context = CGContext(data: nil, width: width, 
            height: height, bitsPerComponent: 8, 
            bytesPerRow: width, space: colorSpace, 
            bitmapInfo: CGImageAlphaInfo.none.rawValue) 
            else { return nil }
        
        // Draw to context
        let destination = CGRect(origin: .zero, size: size)
        context.draw(cgImage, in: destination)
        
        // Return the grayscale image
        guard let imageRef = context.makeImage() 
            else { return nil }
        return UIImage(cgImage: imageRef)
    }
}

Okay, I admit the bitmapInfo is still a little ugly, but isn’t the rest of it grand?

  • No more hacky UIGraphicsBeginImageContext()/UIGraphicsEndImageContext() stuff, let alone getting image from context. Why wasn’t it like this years ago?
  • I do love my Swift constructors. Creating the CGRect from the size is much cleaner now.
  • If you want Core Graphics, Swift gives you Core Graphics: there’s still good reasons to create custom contexts (for example, device gray color spaces) or otherwise work at a low level without having to fire up Accelerate, Core Image, or other power frameworks.
  • You can pair the graphic stack context push with its pop if you do need custom context work. I love defer pairs that prepare for cleanup at the same time you do set-up. (We need to extend Swift reference types to allow paired deinit tasks too!)
  • Swift handles all the memory management. All of it!
  • Swift optionals and errors let you fail so much more gracefully.
  • As you’d probably expect, PDF drawing is just as easy as working with images.
  • The “hoisted” CG utilities (like CGRect’s divided(atDistance:, from:) and the context’s draw and makeImage) are lovely too.

I’m seeing the light at the end of the tunnel for Swift Style wrapping up. Anyone interested in me revisiting “iOS Drawing” for Swift? Or are there other topics you’d rather me follow on?

4 Comments

  • Thanks mate. That’s great stuff. If you have some complete code examples that would be sensational!. There’s not a lot of recent (IOS 10 / Swift 3) stuff out there yet. Cheers.

  • I love the memory management on CG; makes life so much easier.

    I haven’t used defer for the graphics stack, but it does seem to be a good use-case.

    The use of .set() on a UIColor still gives me the heebies after all this time. The procedural pumpkin in me thinks its cleaner, and more obvious to pass a color to a CG setter function.

    Erica, on another topic… loop iteration. What’s the best way to iterate over an array in Swift, while keeping an index?

    e.g. instead of this:

    var index: Int = 0
    for item in items {
    print(“item at index \(index) is \(item)”)
    index += 1
    }

    • If it’s zero-based, just use

      for (index, item) in items.enumerated()
      • Ah! (I feel silly now.) Thanks Erica. 🙂