一,應用需求
動態修改app的圖標,就是在不重新安裝app的情況下,可以修改當前的icon圖標;在某些情況下,是有這個需求的;例如,可以更換主題的app中,一般都會有一套完整的主題包含相應的icon;還有就是一些節日主題的icon或者促銷的icon,例如淘寶、京東等的節日icon。
二,相關API
在iOS 10.3之后,蘋果官方提供了相關的API來實現這個功能,主要是下面這幾個方法:
interface UIApplication (UIAlternateApplicationIcons) // 如果為NO,表示當前進程不支持替換圖標 @property (readonly, nonatomic) BOOL supportsAlternateIcons NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2)); // 傳入nil代表使用主圖標. 完成后的操作將會在任意的后台隊列中異步執行; 如果需要更改UI,請確保在主隊列中執行. - (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)); // 如果alternateIconName為nil,則代表當前使用的是主圖標. @property (nullable, readonly, nonatomic) NSString *alternateIconName NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2)); @end
三,工程內部相關配置
-
配置icon
動態修改的icon不能放在 Assets.xcassets 里,但是正常的主icon還是可以在這里設置的,也可以按下面的方法來設置;
首先,把需要修改的icon放在一個文件夾內:
其文件夾內是這樣的
這里每種icon我只放了一個,如果有多個尺寸的icon,也可以直接全放進去:
然后,文件夾會變成這樣:
這里的icon名稱只需要和下面配置一致即可 -
配置info.plist
- 在info.plist中右鍵 -> Add Row :輸入Icon... 會有提示,選擇Icon files(iOS 5)
這時候,內容是這樣的:這里的Icon files(iOS 5)是個字典,其中可包含的Key值有
CFBundlePrimaryIcon -> Primary Icon CFBundleAlternateIcons UINewsstandIcon -> Newsstand Icon
這里的Primary Icon是設置app的主icon,可以在這里的Icon files數組內添加,有多個的話,依次添加,也可以這里不用填寫,直接在Assets.xcassets 里配置;
下面的Newsstand Icon,暫時用不到,不用管,也可以刪除。
在 Icon files(iOS 5)內添加一個Key: CFBundleAlternateIcons ,類型為字典,在這個字典里配置我們所有需要動態修改的icon:鍵為icon的名稱,值為一個字典(這個字典里包含兩個鍵:CFBundleIconFiles,其值類型為Array,內容為icon的名稱;UIPrerenderedIcon,其值類型為bool,內容為NO,也可以不加此key),例如:把第一步中添加的圖片全部添加進來就是這樣的:
或者將info.plist文件以 Source code 方式打開,添加以下代碼:
<key>CFBundleIcons</key> <dict> <key>CFBundleAlternateIcons</key> <dict> <key>rain</key> <dict> <key>CFBundleIconFiles</key> <array> <string>rain</string> </array> <key>UIPrerenderedIcon</key> <false/> </dict> <key>snow</key> <dict> <key>CFBundleIconFiles</key> <array> <string>snow</string> </array> <key>UIPrerenderedIcon</key> <false/> </dict> <key>sunshine</key> <dict> <key>CFBundleIconFiles</key> <array> <string>sunshine</string> </array> <key>UIPrerenderedIcon</key> <false/> </dict> <key>cloudy</key> <dict> <key>CFBundleIconFiles</key> <array> <string>cloudy</string> </array> <key>UIPrerenderedIcon</key> <false/> </dict> </dict> <key>CFBundlePrimaryIcon</key> <dict> <key>CFBundleIconFiles</key> <array> <string></string> </array> <key>UIPrerenderedIcon</key> <false/> </dict> <key>UINewsstandIcon</key> <dict> <key>CFBundleIconFiles</key> <array> <string></string> </array> <key>UINewsstandBindingType</key> <string>UINewsstandBindingTypeMagazine</string> <key>UINewsstandBindingEdge</key> <string>UINewsstandBindingEdgeLeft</string> </dict> </dict>
如果是添加了多個尺寸icon,也要在這里分別配置,以上面添加的sunshine圖標為例:
使用的時候還是使用sunshine進行賦值即可!
- 在info.plist中右鍵 -> Add Row :輸入Icon... 會有提示,選擇Icon files(iOS 5)
-
代碼配置完成后,代碼部分就比較簡單了:
- (void)changeAppIconWithName:(NSString *)iconName { if (![[UIApplication sharedApplication] supportsAlternateIcons]) { return; } if ([iconName isEqualToString:@""]) { iconName = nil; } [[UIApplication sharedApplication] setAlternateIconName:iconName completionHandler:^(NSError * _Nullable error) { if (error) { NSLog(@"更換app圖標發生錯誤了 : %@",error); } }]; }
在需要修改icon的地方調用這個方法,並把相應的icon名稱傳進去即可:
- (IBAction)snow:(id)sender { [self changeAppIconWithName:@"snow"]; } - (IBAction)rain:(id)sender { [self changeAppIconWithName:@"rain"]; } - (IBAction)cloudy:(id)sender { [self changeAppIconWithName:@"rain"]; } - (IBAction)sunshine:(id)sender { [self changeAppIconWithName:@"sunshine"]; }
示意圖:
-
設置iPad動態圖標
iPad的動態圖標設置和上面步驟基本一樣,有的文章說是將 CFBundleIcons 改為 CFBundleIcons~ipad,即:但是,在測試中發現,使用上面的key值也是可以實現動態改變的,即不做任何修改,iPhone和iPad使用相同的配置,即:CFBundleIcons。
- 去掉更換icon時的彈框
從上面的示意圖可以發現,在設置icon的時候,會有個系統彈框,這樣有時候會不太友好,我們可以使用Runtime,對UIViewController進行擴展來隱藏這個彈框:
// UIViewController+LQNoPresent.h #import <UIKit/UIKit.h> @interface UIViewController (LQNoPresent) @end // UIViewController+LQNoPresent.m #import "UIViewController+LQNoPresent.h" #import <objc/runtime.h> @implementation UIViewController (LQNoPresent) + (void)load { 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(lq_presentViewController:animated:completion:)); method_exchangeImplementations(presentM, presentSwizzlingM); }); } - (void)lq_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion { if ([viewControllerToPresent isKindOfClass:[UIAlertController class]]) { // NSLog(@"title : %@",((UIAlertController *)viewControllerToPresent).title); // NSLog(@"message : %@",((UIAlertController *)viewControllerToPresent).message); UIAlertController *alertController = (UIAlertController *)viewControllerToPresent; if (alertController.title == nil && alertController.message == nil) { return; } } [self lq_presentViewController:viewControllerToPresent animated:flag completion:completion]; } @end
這樣在切換圖標的時候就沒有系統的彈框了: