Generated Asset Catalog Symbols in 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”:
…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:
- You can disable generated asset symbols via the
ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS
over in build settings. Just set it toNO
. Maybe this is useful if you get some symbol clashes or something similar. - 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. - Asset generation, for Swift at least, generates not only constants on
ImageResource
andColorResource
- but also extensions for those, too. So, you could doColor.brandPrimary
too.
What a quality of life win. Hats off to Cupertino & Friends™️.
Until next time ✌️.