原文网址:http://www.cocoachina.com/ios/20170619/19557.html
前言
动态更换App图标这件事,在用户里总是存在需求的:有些用户喜欢“美化”自己的手机。至于用户们喜欢美化到什么程度,这得看个人需求。有的用户想定制个性的App图标,那么各大iPhone论坛里都有方法可以不越狱更改App图标;有的用户想让App图标“动”起来(如系统应用时钟),那么不越个狱还真不好办。
不过今天我们想谈谈苹果官方对于动态更换App图标的支持。
Demo演示:
[替换图标]
Demo地址:https://github.com/maybeisyi/ChangeAppIconDemo
该功能应用的场景
1、白天/夜间模式切换,在切换App主色调同时切换App图标。
2、各类皮肤主题(淘宝就可换肤),附带App图标一块更换。
3、利用App图标表达某种特定功能,如Demo中的,提示当前天气。
4、图标促销提示,如淘宝京东特定节日:11.11、6.18,提前更换App图标。
当然该功能(API)当前只支持iOS10.3以上的系统,所以只能当做一项附加功能来进行使用。下面将详细讲解下如何使用代码来实现此功能。
API方法
1 2 3 4 5 6 7 8 |
|
总共3个方法,简洁明了,不过但看这3个API,我们并不清楚alternateIconName是如何与app图标挂钩的,所以我们需要进一步翻看文档!
文档
shift+command+0打开文档,依次查看3个API,翻译如下:
1.supportsAlternateIcons
supportsAlternateIcons Document
(翻译)只有系统允许改变你的app图标时该值才为YES。你需要在Info.plist文件中的CFBundleIcons这个键内声明可更换的app图标
2.alternateIconName
image.png
(翻译)当系统展示的是你更换后的app图标时,该值即为图标名字(Info.plist中定义的图标名字)。如果展示的是主图标时,这个值为nil。
3.setAlternateIconName:completionHandler:
setAlternateIconName Document
(翻译)alertnateIconName参数:该参数为需要更换的app图标名字,是在你的Info.plist中的CFBundleAlertnateIcons键里定义的。如果你想显示的是用CFBundlePrimaryIcon键所定义的主图标的话,就传入nil。CFBundleAlertnateIcons与CFBundlePrimaryIcon键都是在CFBundleIcons里面定义的。
(翻译)completionHandler参数:该参数用来处理(更换)结果。当系统尝试更改app的图标后,会将结果数据通过该参数传入并执行(该执行过程是在UIKit所提供的队列执行,并非主队列)。该执行过程会携带一个参数:error。如果更换app图标成功,那么这个参数就是nil。如果更换过程中发生了错误,那么该对象会指明错误信息,并且app的图标保持不变。
image.png
(翻译)使用该方法改变app图标为主图标或者可更换的图标。只有在supportsAlternateIcons
的返回值为YES时才能更换。
(翻译)你必须在Info.plist文件的CFBundleIcons键里面声明可以更换的app图标(主图标和可更换图标)。如果需要获取关于可更换图标的配置信息,请查阅 Information Property List Key Reference 里面有关CFBundleIcons的描述。
文档中反复提到了Info.plist文件与CFBundleIcons,这是Xcode6之前是用来配置App图标的老方法,后来有了更完备的Assets.scassets,配置App图标更简单与完善了。不过如今该方法再次被搬上台面,在苹果内部一定也是历经多次“撕逼”后的结果,为何苹果急于在10.3而不是11推出该API?为何苹果不使用Assets.scassets配置可变更的App图标?我们不得而知,不过相信苹果后期会对该配置方法做优化的。
可变更App图标的配置方法
官方配置文档
>
image.png
**该配置文档的内容较多,我们挑重点罗列下(忽略tvOS部分,下同):
Info.plist是个字典,假设为NSDictionary *infoPlist。
CFBundleIcons是Info.plist字典里的一个键@”CFBundleIcons”。
CFBundleIcons对应的value是个字典。
CFBundleIcons里面能够包含的键有:CFBundlePrimaryIcon、CFBundleAlternateIcons、UINewsstandIcon。
让我们用代码展示下这个绕口的结构:
1 2 3 4 5 6 7 8 |
|
image.png
image.png
这是关于CFBundleAlternateIcons的配置文档:
其中有一句话,不仔细思考很难明
In iOS, the value of the key is a dictionary. The key for each dictionary entry is the name of the alternate icon
翻译:该键对应的值是字典,每个字典条目的键都是备用图标的名称。
从这句话中无法很快理清CFBundleAlternateIcons下层的数据结构。实际上这句话表达的意思是:
该键对应的值是字典,这个字典里的每一个键对应的又是一个个字典,而这些键都是备用图标的名称。
让我们把剩余的重点罗列下:
CFBundleAlternateIcons所对应的value是个字典(iOS中),假设为NSDictionary * alertnateIconsDic。
alertnateIconsDic的键,都是备用图标的名字,假设为@”newAppIcon”和@”newAppIcon2″。 @”newAppIcon”的value是个包含CFBundleIconFiles和UIPrerenderedIcon这两个键的字典。
CFBundleIconFiles的value是字符串或者数组(数组内容也为字符串)。字符串的内容为各尺寸备用图标的名字。
UIPrerenderedIcon的value是BOOL值。这个键值所代表的作用在iOS7之后(含iOS7)已失效,在iOS6中可渲染app图标为带高亮效果。所以这个值目前我们可以不用关心。
让我们用代码展示下CFBundleAlternateIcons的value的结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
实际配置文件(Info.plist)
对照着上述的配置文档,我们实际配置完的Info.plist是这样子的:
image.png
当然也要拖入对应的App图标:
image.png
不过这里我们好像还少配置了App主图标,也就是正常情况下我们的图标。按照文档所说,我们需要在CFBundleIcons里面配置CFBundlePrimaryIcon这个主图标对应的内容,但是实际上,我们还是按照老方法,在Assets.xcassets中配置AppIcon,对应尺寸填上对应图片即可。为什么这样子就可以配置主图标呢?让我们来看看某知名电商的ipa(在AppStore上下载的包)内的Info.plist(位于Payload/XXXXXX/Info.plist):
image.png
当然你也可以在你自己App打出的包内进行查看,系统其实是会将Assets.xcassets中配置的AppIcon转化为Info.plist中的CFBundlePrimaryIcon。所以我们主图标的配置方式还是与原先一样。
其他注意事项:
文件扩展名,如@2x,@3x,要么统一不写,那么系统会自动寻找合适的尺寸。要写就需要把每张icon的扩展名写上,和上图的格式一样,在本系列文章的Demo中也有一个单独的Demo示例如何添加多尺寸icon。
iPad版本如果需要有更换的图标,需要在CFBundleIcons?ipad同样设置一次
更换图标后,如何验证iPhone上使用了多尺寸的图标?
image.png
打开DynamicAppIcon(带尺寸)这个Demo。该Demo中,我们在各个尺寸的图标右上角打个”标记“,然后使用上文介绍的setAlternateIconName:completionHandler:
进行图标更换。更换图标的同时,我们再做一件事:
1 2 3 4 5 |
|
这里我们发送了一个本地通知,一会我们就能看到通知上显示的是什么图标了:
image.png
看到图标的区别,也就说明了我们在Info.plist里面设置的多尺寸图标生效了:
image.png
第二部分:[无弹框更换App图标]
什么是弹框
让我们查看弹框的本质
弹框与UIAlertController长的倒是挺像的。让我们来剖析下这个弹框:
image.png
可以看到弹框就是私有类_UIAlertControllerView,让我们再对比下系统的UIAlertController:
image.png
所以更换App时的弹框就是UIAlertController,只不过上面的控件不太一样罢了。(其实我们也能做到在UIAlertController上添加任意控件)
拦截弹框
既然知道了弹框是UIAlertController,那么我们自然而然想到,该弹框是由ViewController通过presentViewController:animated:completion:方法弹出。那么我们就可以通过Method swizzling hook该弹框,不让其进行弹出即可:
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 |
|
这段代码交换了UIViewController的presentViewController:animated:completion:方法。通过打印UIAlertController的特征,我们可以发现,更换App图标时的弹框是没有title与message的,但是我们一般使用的UIAlertController都是带title、message的,毕竟不会弹个空白的框给用户玩。
所以该方法中通过判断title与message来捕捉更换App图标时的弹框,并直接return即可。
总结
其实关于界面上的东西,利用动态特性没有什么是不能做的,苹果既然公开了动态API,我们就可以通过动态方法去了解甚至改造我们想要的东西,如系统控件如何实现等。苹果的”规范“在应用层面其实是无法阻挡开发者步伐的,当然动态特性也不能够滥用(如私有方法),毕竟审核人员才是爸爸!
尽管目前实现了在用户无感的情况下替换App图标,但是可替换的图标还是必须预先放入工程中,并且要在Info.plist内指定。这很大程度上限制了更换图标的动态性:比如我们某天想要推出一款新主题以及对应的App图标,但是新的App图标并没有预先放入工程的main bundle中,也没有在Info.plist中进行指定,所以我们在不上架新版本的情况下,无法推出该新App图标