iOS: Changing Icons with Push Notifications

Starting in iOS 10.3, Apps are able to maintain a set alternate icons which can be switched by the user at runtime. Imagine being able to theme your App Icon based on your home team in a sports app? Or have an icon change to reflect a Sale or campaign, such as Halloween?

Setting up the icons

For this new API, you unfortunately cannot use your XCAssets folder. You instead have to import icon assets using an older technique, by creating a group under Resources.

259

We've included a minimal set for brevity, you should include all icons for all size iPhones and iPads.

Configuring your Property List

Inside your App's Info.plist file, add the following config to declare your main icon and it's alternatives. The file name should not include the @2x/@3x and filetype suffix.

666

Triggering icon changes

Now, the final step is to configure your app to change the icon in a reaction to a push. We'll use Key-Value Payloads to send down the alternate icon's key (the key for the dictionary above) and use the standard api for receiving pushes to react.

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    NSString *iconName = userInfo[@"icon_name"];
    if (!iconName) {
        completionHandler(UIBackgroundFetchResultNoData);
        return;
    }
    
	  // We found this delay necessary in testing
    [self delay:1.0 closure:^{
        [self changeIcon:iconName];
        completionHandler(UIBackgroundFetchResultNewData);
    }];
}

- (void)changeIcon:(NSString *)iconName {
    if (@available(iOS 10.3, *)) {
        if ([UIApplication sharedApplication].supportsAlternateIcons) {
            [[UIApplication sharedApplication] setAlternateIconName:iconName completionHandler:^(NSError * _Nullable error) {
                if (error) {
                    NSLog(@"Error setting icon: %@", error);
                }
            }];
        }
        else {
            NSLog(@"I cannot change icons");
        }
    }
}

- (void)delay:(double)delay closure:(void(^)())closure {
    dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC);
    dispatch_after(when, dispatch_get_main_queue(), closure);
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    guard let iconName = userInfo["icon_name"] as! String? else {
        completionHandler(.noData)
        return
    }

    // We found this delay necessary in testing
    delay(1.0, closure: {
        self.changeIcon(iconName: iconName)
        completionHandler(.newData)
    })
}

func changeIcon(iconName: String) {
    if #available(iOS 10.3, *) {
        if UIApplication.shared.supportsAlternateIcons {
            UIApplication.shared.setAlternateIconName(iconName) { (err:Error?) in
                print("Error setting icon: \(String(describing: err))")
            }
        } else  {
            print("I cannot change icons")
        }
    }
}

func delay(_ delay:Double, closure:@escaping ()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}

Example

495