Alternate app icons with asset catalogues

Its gradually getting more common to see apps offering alternate icons, and its always been pretty easy to do. The only problem is that it was really easy to mess up - lots of manually generated files and entries in a plist. Thanks to a change in Xcode 13, you can drop the plist and speed up adding new icons.

To get started open up your apps build settings, select all and combined, then search for "Asset Catalog Compiler - Options". Enable "Include All App Icon Assets". If you're curious what this does, xcode will now add the needed keys and values to your plist for each app icon you provide, instead of you having to do it all manually.

02-asset-catalogue-alternate-icons-images/01.png

Next, open up your asset catalogue and drag in a new app icon file, with a different name. In this case, i've just called it "AlternateIcon".

Whilst you're in here, add an individual image ( not an App Icon ) called "AlternateIconThumb". You have to do this as once you ship the app via TestFlight, the image will not show via UIImage unless you search through your bundle to find the file ( and having one extra small file is a little easier ). Repeat this for your app icon with "AppIconThumb".

02-asset-catalogue-alternate-icons-images/02.png

The code to set an app icon is incredibly simple. To set it back to the original, just pass nil for the icon name.

let iconName = "AlternateIcon"
UIApplication.shared.setAlternateIconName(iconName, completionHandler: { (err) in
    if err != nil {
        // Show the user an alert
    }
    // Apple shows a confirmation for you
})
let iconName = "AlternateIcon"
UIApplication.shared.setAlternateIconName(iconName, completionHandler: { (err) in
    if err != nil {
        // Show the user an alert
    }
    // Apple shows a confirmation for you
})

One thing to bear in mind - if you want to display these icons in your app, you'll have to use UIImage. UIImage(named: "name") is capable of returning your AppIcons, but Image("name") is not. The easy way around this is just to use a wrapper, like this:

enum AppIcon: String, CaseIterable {
    case standard = "Default"
    case alternate = "Alternate"
    
    /// The name that can be passed to `setAlternateIconName`
    var iconName: String? {
        switch self {
        case .alternate:
            return "AlternateIcon"
        default:
            return nil
        }
    }

    /// The name that can be displayed to the user in release builds
    var iconNameThumb: String? {
        switch self {
        case .alternate:
            return "AlternateIconThumb"
        default:
            return "AppIconThumb"
        }
    }
    
    /// The actual asset, which will fallback to the normal AppIcon if anything is missing.
    var icon: UIImage {
        return UIImage(named: iconNameThumb ?? iconName ?? "AppIcon") ?? UIImage()
    }
}

// Which can be used like...
Image(uiImage: AppIcon.alternate.icon)
    .resizable()
enum AppIcon: String, CaseIterable {
    case standard = "Default"
    case alternate = "Alternate"
    
    /// The name that can be passed to `setAlternateIconName`
    var iconName: String? {
        switch self {
        case .alternate:
            return "AlternateIcon"
        default:
            return nil
        }
    }

    /// The name that can be displayed to the user in release builds
    var iconNameThumb: String? {
        switch self {
        case .alternate:
            return "AlternateIconThumb"
        default:
            return "AppIconThumb"
        }
    }
    
    /// The actual asset, which will fallback to the normal AppIcon if anything is missing.
    var icon: UIImage {
        return UIImage(named: iconNameThumb ?? iconName ?? "AppIcon") ?? UIImage()
    }
}

// Which can be used like...
Image(uiImage: AppIcon.alternate.icon)
    .resizable()

I've put together a quick sample app, so you can see how this works in action.

02-asset-catalogue-alternate-icons-images/03.png

You can grab this from my code samples repo.