read

Changing the app icon is just 1 line of code with setAlternateIconName, but there are many pitfalls, and the lack of guide on how to do it.

The Code

Let’s start with the easy part, which is the code to change the app icon programmatically.

if #available(iOS 10.3, *) {
    let newAppIconName = "AppIcon-2"

    guard UIApplication.shared.alternateIconName != newAppIconName else { return }

    UIApplication.shared.setAlternateIconName(newAppIconName)
}

You might thought you can put the code in app delegate when the app is launched, but no, that will have this un-helpful error:

Error Domain=NSCocoaErrorDomain Code=3072 “The operation was cancelled.”

Pitfall: Code must be called in view controller

You must call setAlternateIconName only in view controller, and in main thread.

Why?

Because the user will be shown this alert:

If you want to run the code in app delegate, make sure it is run in main queue, and visible in the main window.

Pitfall: You cannot avoid the system alert

The alert and the text “You have changed the icon for …” cannot be changed, nor removed.

In Apple Human Interface Guildeline (HIG), they did mention that changing the app icon is a user triggered action.

You can perform tricks to dismiss the alert, or cover with another screen, but that is against the HIG.

This is also why in the code there is a guard statement. It prevents setting the icon name again, and therefore triggering the alert, if it already is that icon.

The Info.plist

Now that you understand the code and how it works, let’s setup the app’s Info.plist.

The following has to be added:

<key>CFBundleIcons</key>
<dict>
  <key>CFBundlePrimaryIcon</key>
  <dict>
    <key>CFBundleIconFiles</key>
    <array>
      <string>AppIcon</string>
    </array>
    <key>UIPrerenderedIcon</key>
    <true/>
  </dict>
  <key>CFBundleAlternateIcons</key>
  <dict>
    <key>AppIcon-2</key>
    <dict>
      <key>CFBundleIconFiles</key>
      <array>
        <string>AppIcon-2</string>
      </array>
      <key>UIPrerenderedIcon</key>
      <true/>
    </dict>
  </dict>
</dict>

The keys are explained, but in short:

  • CFBundleIcons
    • CFBundlePrimaryIcon
    • CFBundleAlternateIcons - dictionary of alternative icons, with the key name as the name that you will use in code eg. “AppIcon-2”

You may wonder what why “AppIcon-2” is repeated in CFBundleIconFiles?

That is actually the image file name (less the “png” or “@2x”). It need not be necessary the same as the key in CFBundleAlternateIcons. For simplicity we used the same name.

Important: If you need to support for iPad, read the last section on a critical pitfall.

Pitfall: Cannot use asset catalog

Strangely, alternative icons CANNOT be added to your asset catalog, unlike the primary icon.

If you do that, you will see an un-helpful error again.

You have to add “AppIcon-2.png”, “AppIcon-2@2x.png”, “AppIcon-2@3x.png” (180x180) to your app target.

Pitfall: You need completionHandler

Although setAlternateIconName can accept a nil completionHandler, as declared in its signature, you shouldn’t because the app with CRASH, if somehow there is error.

As dlbuckley pointed out (as of Aug 2017),

This API isn’t quite ready to be released to us thugs

Pitfall: Not working or Crash in iPad

This is another common one, and critical, because for the neglected iPad.

You could run into error like this:

Error Domain=NSCocoaErrorDomain Code=4 "The file doesn’t exist." 
UserInfo={NSUnderlyingError=0x60000005e0c0 {Error Domain=LSApplicationWorkspaceErrorDomain 
Code=-105 "iconName not found in CFBundleAlternateIcons entry" 
UserInfo={NSLocalizedDescription=iconName not found in CFBundleAlternateIcons entry}}}

In Apple’s obscure documentation, it pointed out:

Important: If your app contains iPad-specific versions of its icons, the system does not fall back to the alternate icons declared in the platform-agnostic version of CFBundleIcons key. Therefore, if you include any alternate icons in the CFBundleIcons key, you must include them again in your CFBundleIcons~ipad variant.

That means, in your “Info.plist”, you need to have a separate entry with CFBundleIcons~ipad key!

So, copy whatever in CFBundleIcons and add for CFBundleIcons~ipad.


Image

@samwize

¯\_(ツ)_/¯

Back to Home