How to achieve dynamic app icon in iOS?

Rushabh Singh
Naukri Engineering
Published in
6 min readMar 28, 2023

Personalisation is a powerful tool in all steps of mobile marketing 📈

You might have observed that some apps change their app icons on special occasions.

Have you ever wondered how they do it without updating your app? 🤔

Before we start the HOW part, Let us discuss WHY we need this thing.

  1. We can use this feature to show a different icon to a premium user rather than showing a regular icon.
  2. An alternative icon could be a dark or light-mode version of the original icon.
  3. Additionally dynamic app icons can be used to promote or advertise special events or festivals providing users with fun & interactive ways to engage your brand.

How to achieve this? 🎯

In iOS 10.3 version, Apple introduced an amazing feature using which developers can now change the app’s icon with predefined additional icons through code, without re-submitting the app to the App store.

Approach:

Step 0: Create alternate app icons of specific sizes

We just need two sizes of our alternate icon

  • ChristmasAppIcon@2x.png = 120 x 120 pixels
  • ChristmasAppIcon@3x.png = 180 x 180 pixels

We need to add @2x and @3x after our icon name for the system to know which one to use in different screens

We are supposed to add these icons inside our project folder and not in the Assets.xcassets, as we do with other icons/images.

We can add multiple alternate app icons, for example

Step 1: Register your new Icons in the Info.plist file

Now we have to add the alternate icon in our Info.plist file.

  1. Add Icon files (iOS 5)/CFBundleIcons to the Info.plist
  2. Add CFBundleAlternateIcons as a Dictionary which will be used for alternative icons

For each alternate icon, add a new entry in the info.plist file, under CFBundleAlternateIcons.

The name of the key will be used in code & the value of the item will be the name of the icon file that we added above in step 0.

Here we have kept the same name for both for easy to use & simplicity.

Step 2: Use Apple APIs to update the icon

We will use below variables & function provided by Apple API to switch app icons


var supportsAlternateIcons: Bool { get }

open var alternateIconName: String? { get }

open func setAlternateIconName(_ alternateIconName: String?, completionHandler: ((Error?) -> Void)? = nil)

supportsAlternateIcons

The value of this property is true only when the system allows you to change the icon of your app. To declare your app’s alternate icons, include them in the CFBundleIcons key of your app’s Info.plist file.

alternateIconName

When the system is displaying one of your app’s alternate icons, the value of this property is the name of the alternate icon (from your app’s Info.plist file). When the system is displaying your app’s primary icon, the value of this property is nil.

func setAlternateIconName( _ alternateIconName: String?, completionHandler: ((Error?) -> Void)? = nil )

Parameters

alternateIconName

The name of the alternate icon, as declared in the CFBundleAlternateIcons key of your app’s Info.plist file. Specify nil if you want to display the app’s primary icon, which you declare using the CFBundlePrimaryIcon key. Both keys are subentries of the CFBundleIcons key in your app’s Info.plist file.

completionHandler

The handler to execute with the results. After attempting to change your app’s icon, the system reports the results by calling your handler. The handler executes on a UIKit-provided queue, and not necessarily on your app’s main queue. The handler has no return value and takes the following parameter:

error

On success, the value of this parameter is nil. If an error occurred, this parameter contains the error object indicating what happened and the value of the alternateIconName property remains unchanged.

Step 3: Create DynamicAppIconHelper to handle the business logic

  1. Create an enum for all the alternate icon
enum NIAppIconType: CaseIterable {
case defaultIcon,
diwaliIcon,
christmasIcon

init(name: String) { //Firebase icon name
switch name {
case "DefaultAppIcon":
self = .defaultIcon
case "DiwaliAppIcon":
self = .diwaliIcon
case "ChristmasAppIcon":
self = .christmasIcon
default:
self = .defaultIcon
}
}

var alternateIconName: String { // given in info p-list
switch self {
case .defaultIcon:
return "DefaultAppIcon"
case .diwaliIcon:
return "DiwaliAppIcon"
case .christmasIcon:
return "ChristmasAppIcon"
}
}

}

2. We are using Firebase remote config to handle the configurations of app icon.

struct DynamicAppIconModel:Codable{
let startDateTS:Double
let endDateTS: Double
let iconName: String
}

We will provide the duration between which we want to show the specific app icon using start date & end date time stamps along with the icon name.

3. Handle business logic (This is where the magic happens 🎬)

We will fetch the DynamicAppIconModel JSON from Firebase Remote config & store it in UserDefault.

Now we need to check the current timestamp & compare it with start time and end time.

func checkForAppIconUpdate(){
if let model = UserDefaultsHelper.getDynamicAppIconModel(){
let currentTS = Date().timeIntervalSince1970
if model.startDateTS <= currentTS && model.endDateTS > currentTS && !model.iconName.isEmpty{
let alternateIcon = NIAppIconType(name: model.iconName)
setIcon(alternateIcon)
}else{
setIcon(.defaultIcon)
}
}else{
setIcon(.defaultIcon)
}
}

If the current timestamp lies in between then check whether the alternate icon is already set or not. Before we try to update the app icon we need to check whether it is allowed by the system.

func setIcon(_ appIcon: NIAppIconType) {
if currentAppIcon != appIcon && UIApplication.shared.supportsAlternateIcons{
UIApplication.shared.setAlternateIconName(appIcon.alternateIconName) { error in
if let error = error {
print("Error setting alternate icon \(appIcon.alternateIconName ): \(error.localizedDescription)")
}
}
}
}

When we switch the app icon, iOS displays an alert to the user informing that the icon has changed.

Can we avoid the alert?

If you want to avoid this alert, you can use the below code where we are using the private method.

func setIconWithoutAlert(_ appIcon: NIAppIconType) {
if UIApplication.shared.responds(to: #selector(getter: UIApplication.supportsAlternateIcons)) && UIApplication.shared.supportsAlternateIcons {
typealias setAlternateIconName = @convention(c) (NSObject, Selector, NSString, @escaping (NSError) -> ()) -> ()
let selectorString = "_setAlternateIconName:completionHandler:"
let selector = NSSelectorFromString(selectorString)
let imp = UIApplication.shared.method(for: selector)
let method = unsafeBitCast(imp, to: setAlternateIconName.self)
method(UIApplication.shared, selector, appIcon.alternateIconName as NSString, { _ in })
}
}

This is little bit risky because if Apple change it’s native method name, your app will start crashing, so use at your own risk ;-)

Complete code:

import Foundation

struct DynamicAppIconModel:Codable{
let startDateTS:Double
let endDateTS: Double
let iconName: String
var shouldInformUser: Bool = true
}

enum NIAppIconType: CaseIterable {
case defaultIcon,
diwaliIcon,
christmasIcon

init(name: String) { //Firebase icon name
switch name {
case "DefaultAppIcon":
self = .defaultIcon
case "DiwaliAppIcon":
self = .diwaliIcon
case "ChristmasAppIcon":
self = .christmasIcon
default:
self = .defaultIcon
}
}

var alternateIconName: String { // given in info p-list
switch self {
case .defaultIcon:
return "DefaultAppIcon"
case .diwaliIcon:
return "DiwaliAppIcon"
case .christmasIcon:
return "ChristmasAppIcon"
}
}

}

struct NIDynamicAppIconHelper{

var currentAppIcon: NIAppIconType {
return NIAppIconType.allCases.first(where: {
$0.alternateIconName == UIApplication.shared.alternateIconName
}) ?? .defaultIcon
}

func checkForAppIconUpdate(){
if let model = UserDefaultsHelper.getDynamicAppIconModel(){
let currentTS = Date().timeIntervalSince1970
if model.startDateTS <= currentTS && model.endDateTS > currentTS && !model.iconName.isEmpty{
let alternateIcon = NIAppIconType(name: model.iconName)
setIcon(alternateIcon)
}else{
setIcon(.defaultIcon)
}
}else{
setIcon(.defaultIcon)
}
}

func setIcon(_ appIcon: NIAppIconType) {
if currentAppIcon != appIcon && UIApplication.shared.supportsAlternateIcons{
UIApplication.shared.setAlternateIconName(appIcon.alternateIconName) { error in
if let error = error {
print("Error setting alternate icon \(appIcon.alternateIconName ): \(error.localizedDescription)")
}
}
}
}

}

References:

--

--

Rushabh Singh
Naukri Engineering

Moving fast without breaking things 👨‍💻……. Exploring Mobile Apps development