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.
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.
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
Updated less than a minute ago