1,看需求:在节日活动期间通常会看到某些大厂的AppIcon主动就变化了,咱们也需要在节日期间让用户不更新新版别的情况下,切换咱们的AppIcon。

2,列计划:1,热更新 2,发版别并且强制更新 3,AppIcon主动切换。

3,选计划:3

4,具体实现:

1,配图:把图片拖进工程资源文件夹,并取个高雅且适宜的姓名,如下图:

iOS动态切换AppIcon

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的称号。如下图:

iOS动态切换AppIcon

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了。

iOS动态切换AppIcon