1,看需求:在节日活动期间通常会看到某些大厂的AppIcon自动就变化了,我们也需要在节日期间让用户不更新新版本的情况下,切换我们的AppIcon。
2,列方案:1,热更新 2,发版本并且强制更新 3,AppIcon自动切换。
3,选方案:3
4,具体实现:
1,配图:把图片拖进工程资源文件夹,并取个优雅且合适的名字,如下图:
2,配置Info.plist文件,添加Icon files (iOS 5), 它是个字典,其中默认有两个Key值,分别是:
**Primary Icon(主icon):**设置app的主icon,可以在这里的Icon files数组内添加,有多个的话,依次添加,也可以这里不用填写,直接在Assets.xcassets 里配置;
**Newsstand Icon(期刊icon):**设置所有用户订阅的报刊和杂志类的图标,目前我们用不到,先不用管。
重点:在 Icon files (iOS 5),内添加一个Key: CFBundleAlternateIcons ,类型为Dictionary。****在这个字典里配置我们所有需要动态修改的AppIcon:键为AppIcon的名称,值为一个字典(这个字典里包含两个键:CFBundleIconFiles,其值类型为Array,内容为icon的名称。如下图:
3,代码编写:我用的****Swift,这个代码MSObjectTools.exchangeAlternateIcon(withName: iconName)
是用来替换原有的方法,就是注释掉的那部分。如果不替换的话,每次切换Icon成功之后,会有一个弹窗提示很烦。暂时没找到Swift替换IMP的方法。所以引入了一个OC的类。
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
///注册后端云
self.registerLearnCloud()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self .changeAppIcon(with: "appIcon_earth")
}
return true
}
更换图标方法
///更换图标方法
func changeAppIcon(with name: String?) {
guard UIApplication.shared.supportsAlternateIcons else {
return
}
guard let iconName = name, iconName.count > 0 else {
return
}
MSObjectTools.exchangeAlternateIcon(withName: iconName)
// UIApplication.shared.setAlternateIconName(iconName) { error in
// print("切换图标出错,原因:\(error.debugDescription)")
// }
}
替换IMP方法
#import <UIKit/UIKit.h>
#import "MSObjectTools.h"
@implementation MSObjectTools
///执行换图标的方法
+ (void)exchangeAlternateIconWithName:(NSString *)iconName {
if ([[UIApplication sharedApplication] respondsToSelector:@selector(supportsAlternateIcons)] &&
[[UIApplication sharedApplication] supportsAlternateIcons])
{
NSMutableString *selectorString = [[NSMutableString alloc] initWithCapacity:40];
[selectorString appendString:@"_setAlternate"];
[selectorString appendString:@"IconName:"];
[selectorString appendString:@"completionHandler:"];
SEL selector = NSSelectorFromString(selectorString);
IMP imp = [[UIApplication sharedApplication] methodForSelector:selector];
void (*func)(id, SEL, id, id) = (void *)imp;
if (func)
{
func([UIApplication sharedApplication], selector, iconName, ^(NSError * _Nullable error) {});
}
}
}
@end
4,注意事项:
-
1,不能在didFinishLaunchingWithOptions调用这个方法,如果必须在didFinishLaunchingWithOptions里调用的话,必须加延时,否则会报被取消Error
-
2,setAlternateIconName 这个方法在10.3以后才有,注意系统版本。
-
3,icon资源文件需要在项目目录下,不能是Assets.xcassets中的图片,否则无效。
后续优化:
我在调用 func changeAppIcon(with name: String?)
这个方法之后,活动结束,发现无法还原。于是做出了如下优化,把完成结果通过block回调出来:
///执行换图标的方法
+ (void)exchangeAlternateIconWithName:(NSString *)iconName completeBlock:(void (^)(NSError * _Nullable error))completed {
if ([[UIApplication sharedApplication] respondsToSelector:@selector(supportsAlternateIcons)] &&
[[UIApplication sharedApplication] supportsAlternateIcons])
{
NSMutableString *selectorString = [[NSMutableString alloc] initWithCapacity:40];
[selectorString appendString:@"_setAlternate"];
[selectorString appendString:@"IconName:"];
[selectorString appendString:@"completionHandler:"];
SEL selector = NSSelectorFromString(selectorString);
IMP imp = [[UIApplication sharedApplication] methodForSelector:selector];
void (*func)(id, SEL, id, id) = (void *)imp;
if (func)
{
func([UIApplication sharedApplication], selector, iconName, ^(NSError * _Nullable error) {
completed(error);
});
}
}
}
调用的时候
///更换系统图标
func changeAppIcon(with name: String?) {
if name == UserDefaults.MSOtherRecordInfo.string(forKey: .autoAppIcon) {
return
}
if #available(iOS 10.3, *) {
guard UIApplication.shared.supportsAlternateIcons else {
return
}
guard let iconName = name, iconName.count > 0 else {
return
}
MSObjectTools.exchangeAlternateIcon(withName: iconName) { error in
if error == nil {
UserDefaults.MSOtherRecordInfo.set(stringValue: iconName, forKey: .autoAppIcon)
}
}
}
}
先在UserDefault里记录下替换AppIcon的图片名称,如果名称相同的话,就不再进行替换了。
如果活动结束,想换回之前的,需要一张和AppIcon同样的图片。比如我的appIcon_earth是活动期间的用的,appIcon_default是和App主图标一样的图片。如果活动结束,后台接口把图片名字配置成appicon_default,就OK了。