4 Xcode Asset Catalog Secrets You Need to Know


The Nightmare

So you're working on a project. You get a request from the higher ups for you to put a couple of images in your project. You go to the designer on your team (if you have one) and you suck up the courage to ask them for this image. Of course, they're normally too busy to help you because, well, let's face it; they have more work to do than you do. Seriously, have you ever seen a tech designer's workload?! It's RIDICULOUS. And the absolute LAST thing you want to do is be the guy that adds to that workload. Especially when the workload you're adding is something so simple to a designer that giving them this workload is more of a nuisance than actual work. Not to mention, they have to put it on a queue so you're left waiting for days to get back a modified image set. Let's explore some of these nuisances and how we can make them better with Xcode's Asset Catalogs.

Nuisance Numba 1:
"Could You Change the Color of this Image?"

iOS so far has given us some pretty complicated ways to work with images in our projects. Unfortunately, there's no way to get around this as iOS developers. I can't tell you how many times we've changed colors on a project for whatever reason be it accessibility support or because the client didn't like the current colors on a project. This isn't too much of a problem since we can change our UIColors in a project wide refactor. But then, we're left with changing our image's colors in a project. Which means we have to go back to our overly busy designers to just change colors on our images because we're too lazy to learn how to work Photoshop. We could do it programmatically of course, but the code for it looked terrible and complicated. Not to mention how easy it is to leak certain objects when manipulating our images. Before iOS 7, changing the colors of your image programmatically looked something like this (there are several ways to do this by the way):

class func image(name: String, withColor color: UIColor) -> UIImage? {
    if var image = UIImage(named: name) {
        // begin a new image context, to draw our colored image onto. Passing in zero for scale tells the system to take from the current device's screen scale.
        UIGraphicsBeginImageContext(image.size, false, 0)

        // get a reference to that context we created
        let context = UIGraphicsGetCurrentContext()

        // set the context's fill color
        color.setFill()

        // translate/flip the graphics context (for transforming from CoreGraphics coordinates to default UI coordinates. The Y axis is flipped on regular coordinate systems)
        CGContextTranslateCTM(context, 0.0, image.size.height)
        CGContextScaleCTM(context, 1.0, -1.0)

         // set the blend mode to color burn so we can overlay our color to our image.
        CGContextSetBlendMode(context, kCGBlendModeColorBurn)
        let rect = CGRect(origin: CGPointZero, size: image.size)
        CGContextDrawImage(context, rect, image.CGImage)

        // set a mask that matches the rect of the image, then draw the color burned context path.
        CGContextClipToMask(context, rect, image.CGImage)
        CGContextAddRect(context, rect)
        CGContextDrawPath(context, kCGPathFill)

        // generate a new UIImage from the graphics context we've been drawing onto
        image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return image
    }
    return nil
}

Two whole OS upgrades later, I'm still seeing code like this. As of iOS 7, we have imageWithRenderingMode - AKA the hero we all need but don't deserve! Our imageWithRenderingMode is a function on UIImage that accepts a UIImageRenderingMode enum value that has three options:

typedef NS_ENUM(NSInteger, UIImageRenderingMode) {
    UIImageRenderingModeAutomatic,          // Use the default rendering mode for the context where the image is used
    UIImageRenderingModeAlwaysOriginal,     // Always draw the original image, without treating it as a template
    UIImageRenderingModeAlwaysTemplate,     // Always draw the image as a template image, ignoring its color information
} NS_ENUM_AVAILABLE_IOS(7_0);
  • UIImageRenderingModeAlwaysOriginal - Just like the option sounds, this mode tells the system to render your image exactly as it looks in your image's file.
  • UIImageRenderingModeAlwaysTemplate - This mode is our funnest mode. It scans your image, and creates a stencil/template from all pixels in your image that aren't transparent. It also ignores all color information in your image and uses the tintColor property on your UIView subclasses to fill these template pixels with the color of your choice.
  • UIImageRenderingModeAutomatic - This mode allows the system to decide how to render your image based on the image's environment. If your image is placed within places like UITabBars, UINavigationBars, UIToolbars, and UISegmentedControls, then your image will be rendered as AlwaysTemplate. Everywhere else will use the AlwaysOriginal rendering mode.

Knowing this, our above image coloring code can now be boiled down to this:

if var imageToChange = imageView.image?.imageWithRenderingMode(.AlwaysTemplate) {
    imageView.image = imageToChange
    imageView.tintColor = .redColor() //Setting the tint color is what changes the color of the image itself!
}

It's like magic isn't it?! If you want to do this programmatically, it's now so much easier to do.

BUT WAIT, THERE'S MORE! And 10 bucks says I can do this without code.

Since Xcode 6 and up, imageWithRenderingMode has been rolled into Xcode's Asset Catalogs. If you select your image in your Image Asset Catalog, go to the Attributes Inspector on the right hand side, you can actually change the 'Render As' option to 'Template Image' like so:

And that's literally it. You can even use this to change the color of that image in a UIImageView inside of a storyboard by changing the tintColor property in the Storyboards Attributes Pane!

Nuisance Numba 2:
"Could I get this Image in 3x resolution?"

Now this one sucks because all designers get this question for every app in the history of mankind at least once a year. It seems as though Apple comes out with a new screen resolution every year and I doubt this year is going to be any different. As hardware technology increases, Apple is going to stay ahead of the curve and pack as many pixels per inch possible in their screens. This unfortunately means that we can't just go into Preview and blow up our already existing images because this can cause bad things like pixelation and anti-aliasing. In lamens terms, our images get ugly, yo'. Everytime a designer is asked to create an already existing image at 3x resolution, a unicorn somewhere dies. This actually explains why we no longer see these marvelous creatures anymore.

So in what is possibly the best news I've heard last year at WWDC, Xcode 6+ now supports vector PDFs in Asset Catalogs. Your designers will know what this means but essentially, PDFs are the de facto standard for vector assets. A vector file contains a lot of metadata of an asset that tells the system how to render it's contents, independent of the screen's resolution. Boiled down as simply as possible, an image of a circle in a vector PDF will be just as crisp and clear when rendered at 5 pixels wide as it will be when rendered at 5,000,000 pixels wide without seeing strange artifacts or pixelation.

How Xcode does this is by generating your 1x, 2x, and 3x images at compile time based off of the size of your vector PDF's assets for iOS platforms. If your vector PDF image is 45x45px, then Xcode will generate three PNGs at compile time:

  • 45x45px for 1x devices (iPhone 3G and 3GS)
  • 90x90px for 2x/Retina Display devices (iPhone 4, 4S, 5, 5S, and 6)
  • 135x135px for 3x devices (iPhone 6 Plus and up)

This also means that whenever we get larger screen resolutions, Xcode will be able to scale up your images from your already existing vector PDF for you, giving you automatic support for future devices for free. Aaaannndd, if you happen to be an OS X developer, then it gets even better for you! Vector PDFs have full support in OS X apps, giving you complete ability to scale up your images in code without distorting it. 👍🏻

To get this support in Xcode 6, all you have to do is get the vector PDF files from your friendly neighborhood designer and toggle the 'Scale Factor' dropdown in your Asset Catalog Attribtues Pane to Single Vector:

You can just drag your PDF into the Asset Catalog in Xcode and go from there.

You can just drag your PDF into the Asset Catalog in Xcode and go from there.

Nuisance Numba 3:


"Can I get launch screens for the new devices?"

Launch Screens are pretty important to an app. It's the first thing you see when launching an app, and it gives a pretty good first impression of how the rest of your app is designed. Maybe it's just me, but if I see a poorly designed launch screen, I tend to scrutinize the rest of the app even more. As for our poor designers, everytime a new device comes out, they also know that they need to get out larger launch screens so the new device's resolution is supported. In the case of iPhone 6 and up, the application went into a weird zoom mode if you didn't have a launch screen image made for the iPhone 6 and 6 Plus! Launch Screens are still in Asset Catalogs, but I suggest taking them out because they have evolved now too. Now, you can use LaunchScreen xibs. Please. Use them. They are your friends.

If you go to your project file, you can specify an actual .xib for the app to load at launch, effectively eliminating the need for having up to 9 launch screen images. The Launch Screen .xib can even support AutoLayout so we can now build out our Launch Screens piece by piece rather than having a dozen or so images in an Asset Catalog somewhere. Here's how to set this baby up:

First, create a .xib file. Xcode has a neat Launch Screen XIB type you can choose from:

Then, go to your project file, select your app's target, and make sure your new Launch Screen .xib file is specified as your Launch Screen file:

Try to take as much advantage of this while you can. You really aren't going to want to be caught asking a designer for an 8x launch screen for the new phablet that comes out a year from now.

Nuisance Numba 4:
"Can I get those images for these buttons, but longer?"

This actually happens more frequently than you think it does. Perhaps it's a pattern image, or even the image of a button with some rounded corners that you need resized because, again, you have a larger screen that you need to account for and you don't want your button to stretch all weird like. Natasha has a great post on this on how to do this programmatically, but we can also do this same thing with Xcode 6's Asset Catalogs. Bee-tee-dubs, I highly suggest you read that linked article before continuing so you understand what's happening. Disclaimer: images and such have been shamelessly taken from her article. Sorry, Natasha!

Alright. Moving on!

Before, having resizable images was a matter of having code that looked like this:

let edgeInsets = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0)
let backgroundButtonImage = UIImage(named:"purple_button")?.resizableImageWithCapInsets(edgeInsets)

purpleButton.setBackgroundImage(backgroundButtonImage, forState: .Normal)

Which could take an image that looked like this:

And stretch out the portion 8 pixels towards the middle of the image within a UIImageView's container's bounds at runtime so it can preserve it's rounded corners to look like this:

Thankfully, we don't even need code to have this happen due to the slice and dice enhancements in Xcode's Asset Catalogs. First, go to the bottom right corner of your image's screen in Xcode and click on 'Show Slicing':

You should now see the slicing pane and a button that says "Start Slicing".

After you click on that button, you'll be presented with three options that look like this:

The left button is for horizontal edge insets, the right button is for vertical edge insets, and the center is for both. In the case of our square, we want to preserve our rounded corners, so we're going to click the middle button to tell our system we want the center of our button to stretch both horizontally and vertically. Once we do, we're going to see some bars that are draggable that allow us to further refine where we want the image to start resizing:

As you can see, the dark purple areas are going to be preserved by the system, while the light purple areas will be the pixels that stretch. 

As you can see, the dark purple areas are going to be preserved by the system, while the light purple areas will be the pixels that stretch. 

What's even more awesome is that Xcode actually found our rounded corners for us so we don't have to figure out where to start our stretching of the image! One last thing you can't forget is to make sure you let Xcode know that the image is a resizable image with cap insets in the attribtues pane like so:

Untitled-9.jpg

If I were you, I would highly suggest playing with this feature and getting comfortable with it. It can be an invaluable tool in your arsenal that can prevent you from having magic numbers in your resizableImageWithCapInsets code and help decouple your view logic from your app logic!

Conclusion

I'm pretty sure there are other things that we as developers do to annoy our fellow graphic designers almost daily, but at least we can take some of these features and give them a little break from time to time. After all, we can program almost anything so why not take advantage? Doing so will just make us better coworkers and humans in the process. And so with that, I bid you farewell for now!

Happy coding fellow nerds!