Manage Your iOS Resources Type-Safely with R.swift

A common frustration with the iOS platform is that resources are accessed using magic strings. In practice, this means you'll find out if an image, icon, localized string etc. exists at runtime. Either by seeing what you were hoping for or getting a hard crash. Let's fix this by introducing your new best friend - R.swift.

Written by 

The developers behind the iOS platform should take a close look at how Android is handling resources (localized strings, images, icons, etc.). No resource references using magic strings - everything is statically typed as it should be. But don't worry - the iOS community comes to the rescue by providing a library for this missing feature in the platform.

In this post, we will uncover the tool R.swift that allows for accessing resources in a clean and type-safe way - and how you incorporate this tool in your iOS projects.

Why should you use R.swift?

R.swift provides strong typed, autocompleted resources like images, fonts and segues in Swift projects.

Let us look at an example to help paint the picture:

// Traditional way of accessing assets using strings
// Consider what should happen if the image isn't there at runtime
lazy var unsafeBackgroundImage: UIImageView = {
    guard let image = UIImage(named: "matteo-catanese.jpg") else {
        fatalError("Ouch, it wasn't there.") 
    }
    let imageView = UIImageView(image: image)
    imageView.frame = containerView.frame
    return imageView
}()

// New way of accessing assets using R.swift
// You'll get an error at compile-time if the image isn't there
lazy var safeBackgroundImage: UIImageView = {
    let imageView = UIImageView(image: R.image.matteoCatanese())
    imageView.frame = containerView.frame
    return imageView
}()

Not only will you get an error compile-time, if you're referencing incorrect resources, but you will have autocompletion, so you never again have to guess the exact name of that icon, font, etc.

The way R.swift works is by scanning the project for resources during a build phase. It then generates an R.generated.swift file containing one struct per resource type (R.image, R.font, R.string and so on) with properties for each specific resource in the project.

R.swift currently supports the following resources: images, custom fonts, resource files, reusable cells, localized strings, storyboards, segues, and nibs.

Setting up R.swift using Mint

My preferred way of setting up R.swift in a project is by using Mint. Mint is great for most CLI packages that you would normally download with Homebrew, as you can easily download and run a specific version of a package. This way, we can easily align each team member's development environment and reduce those annoying "it works on my machine"-issues.

First off, install Mint with Homebrew (as ironic that may be) by running brew install mint in your terminal. Then you want to add a so-called Mintfile in the root of your project. It should contain one line:

https://github.com/mac-cain13/R.swift.git@v5.1.0

Normally, you would add a library by using a format like mac-cain13/R.swift@5.1.0 but support for Mint was added recently and seems to still have some early quirks. The line above will do the job for now.

You'll then need to run mint bootstrap in the root of your project to install R.swift. Now, open the project and add the package R.swift.Library to your project via Swift Package Manager. This package will provide types to support the R.swift code generation in your project.

Once that's installed, you need to run the R.swift tool from a build phase, so that new resources become available with R.swift after each build. Go to the target of your app, select Build Phases and create a new phase just above Compile Sources. Paste the following script to be run by the build phase:

if mint list | grep -q 'R.swift'; then
  mint run R.swift rswift generate "$SRCROOT/YourProjectName/Resources/R.generated.swift"
else
  echo "error: R.swift not installed; run 'mint bootstrap' to install"
  return -1
fi

Next, you want to add this line to the Input Files:

$TEMP_DIR/rswift-lastrun

And this line to the Output Files (assuming you want the R.generated.swift in a Resources folder).

$SRCROOT/YourProjectName/Resources/R.generated.swift

The build phase should look like this:

Finally, create a Resources group in your project. Build your project, and in Finder, you will now see an R.generated.swift in the root folder.

Go ahead and drag the R.generated.swift file into your project and uncheck Copy items if needed. You should end up with a folder structure similar to this:

Optional: Add *.generated.swift to your .gitignore file to prevent merge conflicts.

We're done! Now, you should have autocompletion by using R.swift in your codebase.

Alternatives

There are various alternatives to R.swift. The most prominent one is SwiftGen, but the main reason that I use R.swift is that it inspects the Xcodeproj file for resources instead of asking you for files to use. That makes my part of the process a bit easier.

It also seems that R.swift is more actively maintained at the moment. The result is fairly similar, and if you're currently using SwiftGen or another alternative that gets the job done, you might as well stick to that.

Conclusion

That's it! We covered how you get started with R.swift in your iOS projects to achieve:

  • Compile-time validation - no more incorrect strings that will crash your app at runtime
  • Free autocompletion - never have to guess an asset name again

Thanks for reading. See you in another article!

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.