lead
In IOS 10.3, apple provides an interesting feature.
I don't know if you have noticed. The calendar and App icons on the iPhone always display the date and time of the day in real time, and the seconds of time will move. In fact, after installing the App, you can freely change the App icon. Now, our common developers can also realize it. However, this can only be implemented after IOS 10.3.
The effects are as follows:

You can see that the icon of the App is replaced after clicking the button.
This effect can be used in many places to make apps that need more timeliness, such as calendar, time, weather, bills, activities, etc.
API support
First of all, this function is only supported after iOS 10.3, so we certainly need to make a judgment before using it. Of course, we can obtain the system version of iOS to decide, but Apple provides us with a direct judgment method:
// If it is NO, it means that the current process does not support the replacement icon. If YES, it does @property (readonly, nonatomic) BOOL supportsAlternateIcons NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2));
Therefore, we can directly use the supportsAlternateIcons attribute to judge before use.
The remaining problem is how to set it. Apple also directly provides a simple and easy-to-use method to use it. Alterateiconname is the passed in picture name to be used as an icon, and completionHandler is the code block after execution:
// Alternateiconame is nil, representing the use of the main icon. The completed operations will be executed asynchronously in any background queue; If you need to change the UI, be sure to execute it in the main queue. - (void)setAlternateIconName:(nullable NSString *)alternateIconName completionHandler:(nullable void (^)(NSError *_Nullable error))completionHandler NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2));
Of course, if we want to know which icon is currently used, we can also know it directly through the alternateIconName attribute:
// If alternateiconame is nil, it means that the main icon is currently used @property (nullable, readonly, nonatomic) NSString *alternateIconName NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2));
Through these three API s, we can basically use them smoothly. Before writing code, we also need to configure the Info.plist file, instead of directly dragging the picture to the project. We need to add some fields in Info.plist, as shown in the figure:

The Primary icon is used to place the initial icon. We used to put the icon in Assets, but this key will also be generated. Icon files is an array of pictures. It is reasonable to place pictures of different sizes for devices with different resolutions. Here, I will directly use one picture for convenience.
CFBundleAlternateIcons is to put some icon images that may change. It is a dictionary. There are many sub dictionaries below. The key name of the sub dictionary is actually the name of the image. The value is the same as the Primary above. Put image arrays of different sizes. As for UIPrerenderedIcon, we don't need it. Be sure to note that the key is the name of the picture, so that the corresponding key value pair can be found when calling the above API to pass in the picture name, otherwise the change will fail, and the console will display that the file cannot be found.
usage method
First, we put two buttons on the interface and click the response to change to different icons.
In the button response method, we must first judge whether the current system supports changing icons, and return directly if it does not.
If the system supports it, we will use the method of changing the icon mentioned above to change the image name. In fact, we will find the corresponding image array as the key name in CFBundleAlternateIcons:
- (void)toBoy { if (![[UIApplication sharedApplication] supportsAlternateIcons]) {// The system does not support changing icons return; } [[UIApplication sharedApplication] setAlternateIconName:@"boy.jpg" completionHandler:^(NSError * _Nullable error) { if (error) { NSLog(@"replace app An error occurred with the icon: %@",error); } }]; }
In this way, we have realized the simplest method to change the App icon when the App is running. However, when you click the button to change the icon, a prompt box will pop up:

This experience is not smooth. You can't interrupt users every time. Let's solve this problem.
Remove the prompt box when changing the icon
This prompt box is implemented by UIAlertController, and all uialertcontrollers pop up through the presentViewController: animated: completion: method. We can try to intercept this process. However, UIAlertController is a very common control after all, so we can't affect other uses.
Through observation and testing, it can be found that this pop-up box does not have a title and message. The pop-up box we make usually has at least a title, and message is often used. Therefore, it is special to have neither. We can make special treatment for it according to this situation and prohibit pop-up.
To intercept system methods, we use the method exchange technology in runtime to implement our own presentViewController: animated: completion: method. In our own method, judge whether the title and message of UIAlertController to pop up are nil. If so, we will return directly and will not pop up; If not, it will pop up normally. We can call the implementation of the system. Note that when we exchange methods, we only exchange the method implementation IMP, so after the exchange, if we want to call the implementation of the original system, we need to call our own method name SEL:
#import <objc/runtime.h> - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; // Replace the method of displaying pop-up boxes with runtime static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method presentM = class_getInstanceMethod(self.class, @selector(presentViewController:animated:completion:)); Method presentSwizzlingM = class_getInstanceMethod(self.class, @selector(ox_presentViewController:animated:completion:)); // Exchange method implementation method_exchangeImplementations(presentM, presentSwizzlingM); }); } // Own method of replacing display pop-up box - (void)ox_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion { if ([viewControllerToPresent isKindOfClass:[UIAlertController class]]) {// The UI alert controller to pop up // Output to control to find that both are null NSLog(@"title : %@",((UIAlertController *)viewControllerToPresent).title); NSLog(@"message : %@",((UIAlertController *)viewControllerToPresent).message); // When changing the icon, the title and message of the prompt box are nil, which can be handled specially UIAlertController *alertController = (UIAlertController *)viewControllerToPresent; if (alertController.title == nil && alertController.message == nil) {// Is the prompt for changing the icon return; } else {// Other prompts are handled normally [self ox_presentViewController:viewControllerToPresent animated:flag completion:completion]; return; } } // Other pop ups are handled normally [self ox_presentViewController:viewControllerToPresent animated:flag completion:completion]; }
In this way, you can achieve the effect of the beginning. There is no prompt box and enjoy silky smoothness:

junction
This is just a small demo. It will actually be very interesting if it is actually used, but it is more suitable for apps that want to use icons as windows to display content, or add activity logos on icons in special periods, double eleven, etc.
The changes we have made so far are only to use local images and set Info.plist at the beginning. However, if you want to download the image to replace the icon at any time after publishing, it will be relatively troublesome. However, it is also good. You only need to change the content of Info.plist and increase the key value pair of the image after downloading the image, We can make real dynamic changes accordingly.
But think about the iPhone's own clock App, which can change every second with time. How does this work? This shows that this method of changing icons has long existed, but it has not been opened, and may be different from the way it is opened now. If you are interested, you can study the implementation principle behind several current API s, and then think about how to do the effect of the clock.
Example project: https://github.com/Cloudox/OXChangeAppIconDemo
reference resources: http://daiyi.pro/2017/05/01/ChangeYourAppIcons1/