iOS动态切换AppIcon

5,516 阅读3分钟

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