This post presents a concise method of initializing UIImages without force-unwrapping.

I created and maintain a run-tracking app called RaceRunner. I started this app in early 2015, shortly after I learned Swift, and the codebase was not reflecting the wisdom I have accumulated since then. I therefore recently started cleaning up the codebase. This has been a large project, about which I may write a blog post when I am finished. Describing that cleanup is not my goal for this post. Rather, I wish to share with you, gentle reader, one technique I applied during the cleanup to avoid force-unwrapping UIImages. I hope you find it useful.

The Problem

RaceRunner ships with a total of forty-four PNG images to represent the runner on the map. The runner’s avatar can be a human or a horse. The avatar can be stationary or be running west or east. Here is the PNG for the stationary horse:

Stationary Horse

Stationary Horse Blown up to Comically Large Size for Blog Post

The code for this animation, which RaceRunner’s app preview demonstrates, has been in production for more than 3.5 years, and it works. During the cleanup project, one goal of which was to stop force-unwrapping, I encountered the following code for creating the UIImage for a stationary horse:

private let stationaryIcon = UIImage(named: "stationaryHorse")!

Ugh, !. After elimination of arguably duplicative code in the file, there were three such UIImage initializations with force-unwrapping.

One Solution

To avoid force-unwrapping, I initially did UIImage initializations as follows. Note the use of named constants, another change in my coding style since early 2015.

let stationary = "stationary"
let runnerAvatar = "Runner"

guard let stationaryRunnerIcon = UIImage(named: stationary + runnerAvatar) else {
  fatalError("Could not initialize UIImage named \"\(stationary + runnerAvatar)\".")
}

self.stationaryRunnerIcon = stationaryRunnerIcon

I am using this guard-and-fatalError() approach elsewhere in the codebase because I like how that keyword and function document and make explicit my conviction that something can never be nil. In this case, the UIImage representing the stationary runner can never be nil because the PNG ships with the app, and I’ve verified that the PNG loads correctly during both testing and ordinary usage. When I was force-unwrapping, though, there was ambiguity as to whether I was convinced that the UIImage could never be nil or that I just hadn’t considered that possibility. I saw both situations during the RaceRunner cleanup. In some cases, as in the file described in this post, I was convinced that something couldn’t be nil. In other cases, there were sensible defaults for nil situations, so I added those defaults with the nil-coalescing operator.

A Better Solution

Notwithstanding the elimination of force-unwraps, I was not entirely pleased with my UIImage-initialization cleanup because the three nearly identical guard/fatalError() combos in the file violated the DRY principle. That is, they repeated the logic of “try to initialize and trap if that fails”. Remembering a suggestion I saw, IIRC, in an iOS-developer community to which I belong, I created the following UIImage extension:

//
//  UIImage+named.swift
//

import UIKit

extension UIImage {
  static func named(_ name: String) -> UIImage {
    if let image = UIImage(named: name) {
      return image
    } else {
      fatalError("Could not initialize \(UIImage.self) named \(name).")
    }
  }
}

One could argue that UIImage.named("foo") looks too similar to UIImage(named: "foo") and that the developer could accidentally choose the wrong one via autocomplete. If this were a concern, an alternative function name like absolutelyPositivelyNamedIPinkySwear() might be appropriate. For me, this is not.

Anyways, thanks to this extension, verbose

guard let stationaryRunnerIcon = UIImage(named: stationary + runnerAvatar) else {
    fatalError("Could not initialize UIImage named \"\(stationary + runnerAvatar)\".")
}

became lean-and-mean

stationaryRunnerIcon = UIImage.named(stationary + runnerAvatar)

.

Caveat Lector

RaceRunner was not fit to ship, in 2015, before I verified that all UIImages could be initialized at runtime, and I can conceive of no situation in which they could end up nil. Further, I would prefer that the app crash than show a default UIImage because if there were a programmer error in such fundamental functionality, which a nil UIImage would represent, I would welcome the in-your-face signal of a crash. Thus, this file did not present an opportunity to use the nil-coalescing operator and a default value to avoid force-unwrapping. That said, if you, the reader, choose to use this post’s extension approach for UIImage or some other type, for example guaranteed-good URLs, always consider whether there is a sensible default value. If so, use that and the nil-coalescing operator instead. Here is an example of that.

RaceRunner speaks, to the user, run progress at a configurable interval. American, Australian, Irish, and English (RP) accents are available. The following code initializes an Accent object based on the preference stored in UserDefaults:

accent = Accent(rawValue: storedAccentString) ?? .🇺🇸

Initialization of Accent based on rawValue can fail, but since most RaceRunner users are in the United States, American is a sensible default for accent. I had been force-unwrapping, but, since the cleanup, I use this default value.

Postscript

Reader Olivier Halligon suggested an alternative solution to the problem described in this post: “SwiftGen[,] a tool to auto-generate Swift code for resources of your projects, [making] them type-safe to use.” Using SwiftGen, per the readme, a UIImage can be initialized as follows:

let bananaImage = UIImage(asset: Asset.Exotic.banana)

No force-unwrap! I plan to trial SwiftGen in my next greenfield app.