在iOS10.3系统发布之前, 众所周知, 在App Store上架的APP如果要更换Icon图, 只能更新版本替换;
这次苹果却在iOS10.3系统中加入了了更换应用图标的新功能,当应用安装后,开发者可以为应用提供多个应用图标选择。
用户可以自由的在这些图标之间切换,并及时生效。
这是因为 10.3 里引入了一个新的 API,它允许在 App 运行的时候,通过代码为 app 更换 icon
项目配置
备选Icon
首先你需要将备选的Icon图添加到项目中,
注意:
图片不要放到Assets.xcassets
, 而应该直接放到工程中, 不然可能导致更换Icon时, 找不到图片, 更换失败
在info.plist
的配置中,图片的文件名应该尽量不带 @2x/@3x 后缀扩展名,而让它自动选择
配置info.plist
文件
在info.plist
文件中,添加对应的CFBundleAlternateIcons
的信息
这里也可以查看官方的相关介绍
Source Code
添加方式如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <key>CFBundleAlternateIcons </key> <dict> <key>天天特价</key> <dict> <key>CFBundleIconFiles </key> <array> <string>天天特价</string> </array> <key>UIPrerenderedIcon </key> <false /> </dict> <key>小房子</key> <dict> <key>CFBundleIconFiles </key> <array> <string>小房子</string> </array> <key>UIPrerenderedIcon </key> <false /> </dict> <key>小猫</key> <dict> <key>CFBundleIconFiles </key> <array> <string>小猫</string> </array> <key>UIPrerenderedIcon </key> <false /> </dict> <key>邮件信息</key> <dict> <key>CFBundleIconFiles </key> <array> <string>邮件信息</string> </array> <key>UIPrerenderedIcon </key> <false /> </dict> </dict>
注意事项:
虽然文档中写着「You must declare your app's primary and alternate icons using the CFBundleIcons key of your app's Info.plist file. 」
,但经测试,CFBundlePrimaryIcon
可以省略掉。在工程配置 App Icons and Launch Image
- App Icons Source
中使用 asset catalog
(默认配置),删除 CFBundlePrimaryIcon
的配置也是没有问题的。
省略这个配置的好处是,避免处理 App icon
的尺寸。现在的工程中,大家一般都使用 asset catalog
进行 icon 的配置,而一个 icon 对应有很多尺寸的文件。省略 CFBundlePrimaryIcon
就可以沿用 Asset
中的配置。
如果想设置回默认 icon,在 setAlternateIconName
中传入 nil 即可
API调用 下面我们看一下系统提供的三个API, 这里产看官方文档
1 2 3 4 5 6 7 8 9 10 11 12 var supportsAlternateIcons: Bool var alternateIconName: String? func setAlternateIconName(_ alternateIconName: String?, completionHandler: ((Error?) -> Void)? = nil )
具体的实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if #available(iOS 10.3, *) { guard UIApplication .shared.supportsAlternateIcons else { return } UIApplication .shared.setAlternateIconName(imageStr) { (error) in if error != nil { print(error ?? "更换icon发生错误" ) } else { print("更换成功" ) } } }
消除alert弹窗
动态更换App图标会有弹框, 有时候这个弹框看上去可能会很别扭, 但是这个弹框是系统直接调用弹出的, 我们又如何消除呢
通过层级关系可以看到这个弹框就是一个UIAlertController
, 并且是通过presentViewController:animated:completion:
方法弹出的
所以可以考虑使用runtime
, 拦截并替换该方法, 让更换icon的时候, 不弹
下面看一下具体代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 extension NoAlertChangeViewController { fileprivate func runtimeReplaceAlert() { DispatchQueue.once(token: "UIAlertController" ) { let originalSelector = #selector(present(_:animated:completion:)) let swizzledSelector = #selector(noAlert_present(_:animated:completion:)) let originalMethod = class_getInstanceMethod(NoAlertChangeViewController.self, originalSelector) let swizzledMethod = class_getInstanceMethod(NoAlertChangeViewController.self, swizzledSelector) method_exchangeImplementations(originalMethod!, swizzledMethod!) } } @objc fileprivate func noAlert_present(_ viewControllerToPresent: UIViewController , animated flag: Bool, completion: (() -> Swift.Void)? = nil ) { if viewControllerToPresent.isKind(of: UIAlertController .self) { print("title: \(String(describing: (viewControllerToPresent as? UIAlertController)?.title))" ) print("message: \(String(describing: (viewControllerToPresent as? UIAlertController)?.message))" ) let alertController = viewControllerToPresent as? UIAlertController if alertController?.title == nil && alertController?.message == nil { return } else { noAlert_present(viewControllerToPresent, animated: flag, completion: completion) } } noAlert_present(viewControllerToPresent, animated: flag, completion: completion) } }
这里用到了DispatchQueue.once
, 这个once
是我对DispatchQueue
加了一个扩展
在Swift4.0以后, static dispatch_once_t onceToken;
这个已经不能用了
关于这方面的详细介绍, 大家可以看看我的这篇文章–升级Swift4.0遇到的坑
支持不同尺寸的Icon
一个标准的Icon图集, 需要十几种尺寸, 比如: 20, 29, 40, 60等
对于 info.plist
中的每个 icon
配置,CFBundleIconFiles
的值是一个数组,我们可以在其中填入这十几种规格的图片名称。经测试:
文件的命名没有强制的规则,可以随意取,
数组中的文件名也不关心先后顺序。
总之把对应的文件名填进去即可,它会自动选择合适分辨率的文件(比如在 setting 中显示 icon 时,它会找到提供的数组中分辨率为 29pt 的那个文件)。
具体相关官方文档可参考, 官方介绍
首先, 针对不同的尺寸, 我们要有不同的命名, 具体参考下图
文件扩展名,如@2x,@3x,要么统一不写,那么系统会自动寻找合适的尺寸。
要写就需要把每张icon的扩展名写上,和上图的格式一样
代码中调用图片名, 更不需要加上尺寸:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 if #available(iOS 10.3, *) { guard UIApplication .shared.supportsAlternateIcons else { return } UIApplication .shared.setAlternateIconName("Sunday" ) { (error) in if error != nil { print(error ?? "更换icon发生错误" ) } else { print("更换成功" ) } } }