【iOS】动态更换App图标

栏目: IOS · 发布时间: 7年前

内容简介:【iOS】动态更换App图标

前言

  • 动态更换App图标这件事,在用户里总是存在需求的:有些用户喜欢“美化”自己的手机。至于用户们喜欢美化到什么程度,这得看个人需求。有的用户想定制个性的App图标,那么各大iPhone论坛里都有方法可以不越狱更改App图标;有的用户想让App图标“动”起来(如系统应用时钟),那么不越个狱还真不好办。

  • 不过今天我们想谈谈苹果官方对于动态更换App图标的支持。

Demo演示:

【iOS】动态更换App图标

[替换图标]

Demo地址: https://github.com/maybeisyi/ChangeAppIconDemo

该功能应用的场景

  • 1、白天/夜间模式切换,在切换App主色调同时切换App图标。

  • 2、各类皮肤主题(淘宝就可换肤),附带App图标一块更换。

  • 3、利用App图标表达某种特定功能,如Demo中的,提示当前天气。

  • 4、图标促销提示,如淘宝京东特定节日:11.11、6.18,提前更换App图标。

当然该功能(API)当前只支持iOS10.3以上的系统,所以只能当做一项附加功能来进行使用。下面将详细讲解下如何使用代码来实现此功能。

API与文档

API方法

@interface UIApplication (UIAlternateApplicationIcons)
// 如果为NO,表示当前进程不支持替换图标
@property (readonly, nonatomic) BOOL supportsAlternateIcons NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2));
// 传入nil代表使用主图标. 完成后的操作将会在任意的后台队列中异步执行; 如果需要更改UI,请确保在主队列中执行.
- (void)setAlternateIconName:(nullable NSString *)alternateIconName completionHandler:(nullable void (^)(NSError *_Nullable error))completionHandler NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2));
// 如果alternateIconName为nil,则代表当前使用的是主图标.
@property (nullable, readonly, nonatomic) NSString *alternateIconName NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2));
@end

总共3个方法,简洁明了,不过但看这3个API,我们并不清楚alternateIconName是如何与app图标挂钩的,所以我们需要进一步翻看文档!

文档

shift+command+0打开文档,依次查看3个API,翻译如下:

1.supportsAlternateIcons

【iOS】动态更换App图标

supportsAlternateIcons Document

(翻译)只有系统允许改变你的app图标时该值才为YES。你需要在Info.plist文件中的CFBundleIcons这个键内声明可更换的app图标

2.alternateIconName

【iOS】动态更换App图标

image.png

(翻译)当系统展示的是你更换后的app图标时,该值即为图标名字(Info.plist中定义的图标名字)。如果展示的是主图标时,这个值为nil。

3.setAlternateIconName:completionHandler:

【iOS】动态更换App图标

setAlternateIconName Document

(翻译)alertnateIconName参数:该参数为需要更换的app图标名字,是在你的Info.plist中的CFBundleAlertnateIcons键里定义的。如果你想显示的是用CFBundlePrimaryIcon键所定义的主图标的话,就传入nil。CFBundleAlertnateIcons与CFBundlePrimaryIcon键都是在CFBundleIcons里面定义的。

(翻译)completionHandler参数:该参数用来处理(更换)结果。当系统尝试更改app的图标后,会将结果数据通过该参数传入并执行(该执行过程是在UIKit所提供的队列执行,并非主队列)。该执行过程会携带一个参数:error。如果更换app图标成功,那么这个参数就是nil。如果更换过程中发生了错误,那么该对象会指明错误信息,并且app的图标保持不变。

【iOS】动态更换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图标的配置方法

官方配置文档

>

【iOS】动态更换App图标

image.png

**该配置文档的内容较多,我们挑重点罗列下(忽略tvOS部分,下同):

  • Info.plist是个字典,假设为NSDictionary *infoPlist。

  • CFBundleIcons是Info.plist字典里的一个键@"CFBundleIcons"。

  • CFBundleIcons对应的value是个字典。

  • CFBundleIcons里面能够包含的键有:CFBundlePrimaryIcon、CFBundleAlternateIcons、UINewsstandIcon。

让我们用代码展示下这个绕口的结构:

NSDictionary *infoPlist;
infoPlist = @{
               @"CFBundleIcons" : @{
                                     @"CFBundlePrimaryIcon" : xxx,
                                     @"CFBundleAlternateIcons" : xxx,
                                     @"UINewsstandIcon" : xxx
                                   }
             };

【iOS】动态更换App图标

image.png

【iOS】动态更换App图标

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的结构:

@"CFBundleAlternateIcons" : @{
                               @"newAppIcon" : @{
                                                 @"CFBundleIconFiles" : @[
                                                                            @"newAppIcon"
                                                                         ],
                                                 @"UIPrerenderedIcon" : NO
                                                },
                               @"newAppIcon2" : @{
                                                 @"CFBundleIconFiles" : @[
                                                                            @"newAppIcon2"
                                                                         ],
                                                 @"UIPrerenderedIcon" : NO
                                                 }
                             }

实际配置文件(Info.plist)

对照着上述的配置文档,我们实际配置完的Info.plist是这样子的:

【iOS】动态更换App图标

image.png

当然也要拖入对应的App图标:

【iOS】动态更换App图标

image.png

不过这里我们好像还少配置了App主图标,也就是正常情况下我们的图标。按照文档所说,我们需要在CFBundleIcons里面配置CFBundlePrimaryIcon这个主图标对应的内容,但是实际上,我们还是按照老方法,在Assets.xcassets中配置AppIcon,对应尺寸填上对应图片即可。为什么这样子就可以配置主图标呢?让我们来看看某知名电商的ipa(在AppStore上下载的包)内的Info.plist(位于Payload/XXXXXX/Info.plist):

【iOS】动态更换App图标

image.png

当然你也可以在你自己App打出的包内进行查看,系统其实是会将Assets.xcassets中配置的AppIcon转化为Info.plist中的CFBundlePrimaryIcon。所以我们主图标的配置方式还是与原先一样。

其他注意事项:

文件扩展名,如@2x,@3x,要么统一不写,那么系统会自动寻找合适的尺寸。要写就需要把每张icon的扩展名写上,和上图的格式一样,在本系列文章的Demo中也有一个单独的Demo示例如何添加多尺寸icon。

iPad版本如果需要有更换的图标,需要在CFBundleIcons?ipad同样设置一次

更换图标后,如何验证iPhone上使用了多尺寸的图标?

【iOS】动态更换App图标

image.png

打开 DynamicAppIcon(带尺寸) 这个Demo。该Demo中,我们在各个尺寸的图标右上角打个” 标记 “,然后使用上文介绍的setAlternateIconName:completionHandler:

进行图标更换。更换图标的同时,我们再做一件事:

// 测试推送上是否使用了20尺寸的图标
UILocalNotification *noti = [[UILocalNotification alloc] init];
noti.fireDate = [NSDate dateWithTimeIntervalSinceNow:5];
noti.alertBody = @"我们看看推送上面的App图标";
[[UIApplication sharedApplication] scheduleLocalNotification:noti];

这里我们发送了一个本地通知,一会我们就能看到通知上显示的是什么图标了:

【iOS】动态更换App图标

image.png

看到图标的区别,也就说明了我们在Info.plist里面设置的多尺寸图标生效了:

【iOS】动态更换App图标

image.png

第二部分:[无弹框更换App图标]

什么是弹框

让我们查看弹框的本质

弹框与UIAlertController长的倒是挺像的。让我们来剖析下这个弹框:

【iOS】动态更换App图标

image.png

可以看到弹框就是私有类_UIAlertControllerView,让我们再对比下系统的UIAlertController:

【iOS】动态更换App图标

image.png

所以更换App时的弹框就是UIAlertController,只不过上面的控件不太一样罢了。(其实我们也能做到在UIAlertController上添加任意控件)

拦截弹框

既然知道了弹框是UIAlertController,那么我们自然而然想到,该弹框是由ViewController通过presentViewController:animated:completion:方法弹出。那么我们就可以通过Method swizzling hook该弹框,不让其进行弹出即可:

#import "UIViewController+Present.h"
#import @implementation UIViewController (Present)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method presentM = class_getInstanceMethod(self.class, @selector(presentViewController:animated:completion:));
        Method presentSwizzlingM = class_getInstanceMethod(self.class, @selector(dy_presentViewController:animated:completion:));
        // 交换方法实现
        method_exchangeImplementations(presentM, presentSwizzlingM);
    });
}
- (void)dy_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {

    if ([viewControllerToPresent isKindOfClass:[UIAlertController class]]) {
        NSLog(@"title : %@",((UIAlertController *)viewControllerToPresent).title);
        NSLog(@"message : %@",((UIAlertController *)viewControllerToPresent).message);

        UIAlertController *alertController = (UIAlertController *)viewControllerToPresent;
        if (alertController.title == nil && alertController.message == nil) {
            return;
        } else {
            [self dy_presentViewController:viewControllerToPresent animated:flag completion:completion];
            return;
        }
    }
2
    [self dy_presentViewController:viewControllerToPresent animated:flag completion:completion];
}
@end

这段代码交换了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图标


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Beginning XML with DOM and Ajax

Beginning XML with DOM and Ajax

Sas Jacobs / Apress / 2006-06-05 / USD 39.99

Don't waste time on 1,000-page tomes full of syntax; this book is all you need to get ahead in XML development. Renowned web developer Sas Jacobs presents an essential guide to XML. Beginning XML with......一起来看看 《Beginning XML with DOM and Ajax》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器