Strongly typed Assets in Swift

I don’t know about you, but it never sat right with me that all the localized strings and image assets throughout our iOS apps were ‘stringly’ typed.

How many instances of NSLocalizedString("some.random.stringsfile.key", comments:"") or UIImage(named:"") do you have dotted around your code. This seems like it’s almost destined to cause trouble, or at the very least make your code a pain to read… What if the key changes (especially bad if you are using it in multiple places)? And even worse — what if your localized string has parameters? Furthermore, if your code & .strings or .xcassets files are in a framework, the code to get the image or string is even more unwieldy.

Since autumn last year, all new features of our iOS app here at ShopGun are being written in Swift (we still have a large legacy ObjC codebase that is slowly being replaced). As I’ve been getting more and more swifty in my thinking, doing anything that isn’t strongly typed is an anathema. Also, I’ve been trying to get as much separation of concerns as possible, and the existing single giant .strings file was becoming rather painful to maintain.

So, my goal was to have each component within the app (a component being, for example, a single screen, or related collection of screens) own and maintain all the images and strings that it needs, in a strongly typed form. So each component has its own .strings and .xcassets files — this way the files become very manageable, and if I need to move or remove the component, I am sure that there are no unused assets.

For the strongly typed requirement, I initially played with having static variables on a UIImage extension (so you could write UIImage.myButtonIcon, and enjoy all the wonders of swift’s type inference). This, however, led to the images being available globally, which isn’t the best when we are trying to separate our concerns.

So, the technique I ended up using is to nest an Assets struct inside my component’s View/ViewController class(es). Inside the Assets struct I have a number of static String & UIImage variables, which return localizedStrings and UIImage’s from the component’s .xcasset & .strings files. This allows me, from within my view controller, to write Assets.logo or Assets.title.

As you can see in the above example, there are a number of nice tricks this affords you.

  • I use nested structs for sub-assets. So if, for example, a view controller has multiple types of alerts that can be shown, I nest their titles, subtitles & button names. This allows me to simply write Assets.PermissionDeniedAlert.title — it’s a great way to namespace your assets.
  • As Assets is an object, you can also add private utility functions — if there ends up being a lot of strings or images I generally kill repetition by adding func localizedString(_ key:String)->String or func image(_ name:String)->UIImage to the Assets. This is especially useful at ShopGun, as all of these components are being added to a framework, so the strings and images are not in the main bundle.
  • An asset can be a function rather than a variable. This allows for passing parameters — so localized strings can be easily given a (strongly-typed!) variable, or a different string could be returned depending on state (though in my opinion the logic should be kept to a minimum to not pollute the concept).

Obviously, there are situations where images and strings are shared across components. For this case I also have a standalone SharedAssets struct, that contains everything that can be used in multiple places. As you can see in the above example, I refer to the SharedAssets in the Assets, so that my component’s code only has to know about its local Assets.

All in all, this technique has been working very well, and I’ll be using it for all future components. If you have any thoughts, criticisms, or any techniques of your own to solve this problem, please let me know.