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 paireddeinit
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’sdraw
andmakeImage
) 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
Ah! (I feel silly now.) Thanks Erica. 🙂