How to change custom app icon dynamically

Today I’m going to talk about how to change your app icons on the iOS home screen. So you might ask me, what are the use cases for this feature? You may personalize the app icon to each individual user, the icon itself can deliver useful information at a glance depending how you customize it. As for me, one of the tasks for my internship project is to implement this feature so that clients of each comic can have the option to display their particular comics on users’ home screens. This will not only provide value to our clients, but provide value in our app and allow users to select an alternate app icon. By the way, download the app here for free to get your daily feed of webcomics!

“For some apps, customization is a feature that evokes a personal connection and enhances the user experience.” — Apple

I came across this website where I saw this beautiful notification to present to the user before changing the app icon, so I wanted to recreate it, and the following was my attempt:

How did I do it? On the MLB.com At Bat app, it looked like to me a pop up view with a layer of shadow under it. So for my implementation, I decided to go with adding a Visual Effect View With Blur over the view controller, then adding a UIView on top of the existing view controller. I then added a UIBarButtonItem on the view controller navigation bar and updated the button to a settings icon I found on Icons8.

Instead of directly placing the UIView over the View Controller, I dragged it to the top bar as an extension to the view controller, that way you can design your pop up view better and it will look something like the below (left: dragging the UIView onto the top bar, right: after creating the extension)


Now, we can move onto changing the icon in 3 simple steps.

  1. Create your new icons
  2. Edit your info.plist
  3. Code some magic

Let’s start with the icons, I used this website to resize my images into icons. Make sure the icons are placed loose in your project or in a group, rather than inside your Assets.xcassets! Don’t forget to follow the naming conventions @2x and @3x in order to pick the correct icon for users’ devices. For this example, I am using these example icon files. (They are all regular PNG files, with the @2x being 120x120 and the @3x being 180x180)

Now let’s register your custom icons in Info.plist! Right click on your Info.plist file, and choose Open As -> Source Code. This will reveal the raw XML behind your plist. At the end of your property list you should see the following:

</dict>
</plist>

Directly before </dict>, go ahead and add this:

<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>
app_icon_114x114</string>
</array>
<key>UIPrerenderedIcon</key>
<false/>
</dict>
<key>CFBundleAlternateIcons</key>
<dict>
<key>AppIcon-2</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>
app_icon_purple_120x120</string>
</array>
<key>UIPrerenderedIcon</key>
<false/>
</dict>
<key>AppIcon-3</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>
app_icon_green_120x120</string>
</array>
<key>UIPrerenderedIcon</key>
<false/>
</dict>
</dict>
</dict>

What this code does is that it looks into the CFBundleIcons key, which is a dictionary that defines your primary icon (CFBundlePrimaryIcon) and your alternate icons (CFBundleAlternateIcons). The primary icon key itself is also a dictionary that lists all of its icon files (CFBundleIconFiles), which is an array containing the filename for your primary icon as a string. The alternate icons key (CFBundleAlternateIcons) is a dictionary containing the keys of its children, which are the names of icon images you are using. (Note that this doesn’t need to be their exact filenames, just however you want to reference each icon in your code) Each icon name is a dictionary which defines two keys as CFBundleIconFiles array and UIPrerenderedIcon boolean. UIPrerenderedIcon specifies whether the app’s icon already includes a shine effect, false in this case.

To summarize, CFBundleIcons is a dictionary containing the key CFBundlePrimaryIcon and CFBundleAlternateIcons, each is a dictionary containing the key with your image filename, in which is another dictionary containing of the icon files and shine effect settings.

Finally, let’s code! Now that we have our property list all set up, let’s implement the instance method setAlternateIconName(), which sets an alternate icon or can also be set as nil to display app’s default icon.

As you can see in my storyboard screenshot above, I added a UIBarButtonItem and added an action on it. Once the button is pressed, the pop up view will show up and prompt the user to confirm if they want to change their default icon. For example, you might also want to add a button that changes your app icon to AppIcon-3, like this:

UIApplication.shared.setAlternateIconName("AppIcon-3")

To reset your icon to the primary icon, set the alternate icon to nil:

UIApplication.shared.setAlternateIconName(nil)

Now, the power of #available is that the compiler can now check and enforce API usage on older operating system. @available works similarly in that you specify the iOS release you want to target, and Xcode handles the rest. (We need our iOS version to be 10.3 for this to work seamlessly)

@IBAction func newIconTapped(_ sender: UIButton) {

if #available(iOS 10.3, *) {
  if UIApplication.shared.supportsAlternateIcons {
   UIApplication.shared.setAlternateIconName("AppIcon-3") { error in
     if let error = error {
     print(error.localizedDescription)
     UIApplication.shared.setAlternateIconName(nil)
   }
}
oldIconImage.layer.borderWidth = 0
     newIconImage.layer.borderWidth = 3
     newIconImage.layer.borderColor = defaultBorderColor
   }
} else {

showAlert()

}
}

First, let’s check if our app allows us to switch to an alternate icon by checking the supportsAlternateIcons property on our application. If it does, we will set the alternate icon with the instance method setAlternateIconName and pass in the image name that we declared in our info.plist. You can optionally provide a completion handler to be run when the call finishes. We pass in an Error parameter that will be set to a value error if something goes wrong, and if it does, let’s go ahead and print the localizedDescription on the error. ShowAlert() here is a method I wrote that will display an UIAlertController with a UIAlertAction telling users that they need a software version update.

And there we have it! Thanks for reading. :)

Sources:
https://developer.apple.com/documentation/uikit/uiapplication/2806818-setalternateiconname
https://www.hackingwithswift.com/example-code/uikit/how-to-change-your-app-icon-dynamically-with-setalternateiconname
https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-SW14