[SC]()

iOS. Apple. Indies. Plus Things.

Generated Asset Catalog Symbols in Objective-C

// Written by Jordan Morgan // Sep 14th, 2023 // Read it in about 2 minutes // RE: Objective-C

This post is brought to you by Emerge Tools, the best way to build on mobile.

Sweet harmony, if String-based APIs aren’t the worst? Especially for those us working in Objective-C codebases and using our friend, NSAttributedString, often used to stylize labels in interfaces:

- (NSAttributedString *)configureBrandString {
    NSDictionary *attrs = @{NSForegroundColorAttributeName: [UIColor blueColor]};
    NSAttributedString *brandString = [[NSAttributedString alloc] initWithString:@"Hey there!"
                                                                      attributes:attrs];
    
    return brandString;
}

But wait - [UIColor blueColor] is so boring. Your designer wants to use that fancy brand color, so go for it:

- (NSAttributedString *)configureBrandString {
    NSDictionary *attrs = @{NSForegroundColorAttributeName: [UIColor colorNamed:@"BrandPrmary"]};
    NSAttributedString *brandString = [[NSAttributedString alloc] initWithString:@"Hey there!"
                                                                      attributes:attrs];
    
    return brandString;
}

And crash. Can you even spot it?

We misspelled “BrandPrimary” as “BrandPrmary”, and since Objective-C doesn’t appreciate nil values in a dictionary, we get a nice, lovely runtime crash. Yay for Strings! Apple knew this, and finally asset catalogs can generate your color and image names for you in Xcode 15. No more nil values!

let concatenatedThoughts = """

In fact, this problem of String based asset catalog items bothered me so much, I made a CLI tool to generate symbols for them years ago.

"""

In Swift, it just works. Take this asset catalog, with a color of “BrandPrimary” and an image called “BrandLogo”:

An Asset Catalog in Xcode with a color and image.

…over in Swift - we can’t screw up using them:

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Image(.brandLogo)
            Text("Generated Assets")
                .foregroundStyle(.brandPrimary)
        }
        .padding()
    }
}

So much so that, if I were to delete either of those from the asset catalog, the project wouldn’t build anymore! The static properties off of either ColorResource or ImageResource are removed, so I’d be using code that no longer exists (i.e., .foregroundStyle(.brandPrimary)).

And so it is, Objective-C can reap mostly the same benefits!

Just import “GeneratedAssetSymbols.h” and you’ll gain access to NSString constants representing each symbol:

#import "User.h"
#import "GeneratedAssetSymbols.h"
#import <UIKit/UIKit.h>

@implementation User

- (NSAttributedString *)configureBrandString {
    NSDictionary *attrs = @{NSForegroundColorAttributeName: [UIColor colorNamed:ACColorNameBrandPrimary]};
    NSAttributedString *brandString = [[NSAttributedString alloc] initWithString:@"Hey there!"
                                                                      attributes:attrs];
    
    return brandString;
}

Notice the constant ACColorNameBrandPrimary for the color. It even comes with some lightweight documentation that its Swift counterpart enjoys:

The “BrandPrimary” asset catalog color resource.

It keeps up with changes, too. Rename the asset, delete it - whatever, it regenerates the same as Swift code does.

I freaking love this.

Why? Not because I do cartwheels for Objective-C additions, but because I still maintain a lot of Objective-C code. And in its Wild West World, I battle nil values all the time. Now, my code is a lot safer.

Before we close, here a few notes I caught from the release notes over generated asset symbols:

  1. You can disable generated asset symbols via the ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS over in build settings. Just set it to NO. Maybe this is useful if you get some symbol clashes or something similar.
  2. You can pick and choose which frameworks you want to support with the ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOL_FRAMEWORKS build setting. By default, you get all of the technologies your app uses, but if you only want AppKit, UIKit, or similar - you can do that.
  3. Asset generation, for Swift at least, generates not only constants on ImageResource and ColorResource - but also extensions for those, too. So, you could do Color.brandPrimary too.

What a quality of life win. Hats off to Cupertino & Friends™️.

Until next time ✌️.

···

Spot an issue, anything to add?

Reach Out.