iOS10.3后允許App運(yùn)行中變更App圖標(biāo)

在iOS10.3中,蘋(píng)果提供了一個(gè)比較有意思的功能。

不知道大家注意到?jīng)]有,iPhone自帶的日歷和始終App的圖標(biāo)是實(shí)時(shí)顯示當(dāng)日日期和當(dāng)時(shí)的時(shí)間的,時(shí)間的秒鐘還會(huì)走動(dòng),這其實(shí)就做到了在安裝完App后,還能自由地變更App的圖標(biāo),而現(xiàn)在,我們普通的開(kāi)發(fā)者也可以實(shí)現(xiàn)了。但是這只能在iOS10.3之后實(shí)現(xiàn)。

效果如下:

20170509113129535.gif

可以看到在點(diǎn)擊按鈕操作后將App的圖標(biāo)更換掉了。

這個(gè)效果可以用在很多地方,做出更加需要時(shí)效性的App,比如日歷、時(shí)間、天氣、票據(jù)、活動(dòng)等等。

API支持

首先這個(gè)功能只在iOS10.3以后才支持,所以在使用之前當(dāng)然需要進(jìn)行判斷,我們當(dāng)然也能夠自己獲取iOS的系統(tǒng)版本來(lái)決定,但是蘋(píng)果給我們提供了直接的判斷方法:

// 如果為NO,表示當(dāng)前進(jìn)程不支持替換圖標(biāo),YES則支持
@property (readonly, nonatomic) BOOL supportsAlternateIcons NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2));

所以在使用之前我們直接用這個(gè) supportsAlternateIcons 屬性來(lái)判斷就可以了。

剩下的問(wèn)題就是怎么設(shè)置了,蘋(píng)果也直接提供了一個(gè)簡(jiǎn)單易用的方法來(lái)使用,其中 alternateIconName 是傳入的要作為圖標(biāo)使用的圖片名,completionHandler 是執(zhí)行后的代碼塊:

// alternateIconName 為 nil代表使用主圖標(biāo)。完成后的操作將會(huì)在任意的后臺(tái)隊(duì)列中異步執(zhí)行; 如果需要更改UI,請(qǐng)確保在主隊(duì)列中執(zhí)行。
- (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));

當(dāng)然,我們?nèi)绻胍喇?dāng)前用的是哪個(gè)圖標(biāo),也可以直接通過(guò) alternateIconName 屬性來(lái)獲知:

// 如果alternateIconName為nil,則代表當(dāng)前使用的是主圖標(biāo).
@property (nullable, readonly, nonatomic) NSString *alternateIconName NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2));

通過(guò)這三個(gè)API,我們基本就可以順利的使用了。而在寫(xiě)代碼之前,我們還需要配置 Info.plist 文件,并不是直接把圖片拖到工程里就可以了的。我們需要在 Info.plist 中添加一些字段,如圖所示:

image.png

其中Primary用來(lái)放初始圖標(biāo),我們以前在Assets中放圖標(biāo),其實(shí)也會(huì)生成這個(gè)鍵,Icon files 中是圖片數(shù)組,按理是應(yīng)該放置不同尺寸的圖片,來(lái)供不同分辨率的設(shè)備使用,這里為了方便我就直接用一個(gè)圖片了。

CFBundleAlternateIcons 就是放我們可能會(huì)變化的一些圖標(biāo)圖片了,它是一個(gè)詞典,下面包含很多子詞典,子詞典的鍵名其實(shí)就是圖片的名字,值與上面的 Primary 一樣,放不同尺寸的圖片數(shù)組。至于UIPrerenderedIcon,我們不需要用到。一定要注意鍵就是圖片的名字,這樣在調(diào)用上面的API傳入圖片名時(shí)才能夠找到對(duì)應(yīng)的鍵值對(duì),否則會(huì)變更失敗,控制臺(tái)會(huì)顯示找不到文件。

使用方法

首先我們?cè)诮缑嫔戏艃蓚€(gè)按鈕,點(diǎn)擊響應(yīng)就是要更換成不同的圖標(biāo)。

在按鈕的響應(yīng)方法中,我們首先要判斷當(dāng)前系統(tǒng)支不支持換圖標(biāo),不支持則直接返回。

如果系統(tǒng)支持,我們就用上面說(shuō)到的變更圖標(biāo)的方法去變換,傳入圖片名,其實(shí)是作為 CFBundleAlternateIcons 中的鍵名去找對(duì)應(yīng)的圖片數(shù)組:

- (void)toBoy {
    if (![[UIApplication sharedApplication] supportsAlternateIcons]) {// 系統(tǒng)不支持換圖標(biāo)
        return;
    }
    
    [[UIApplication sharedApplication] setAlternateIconName:@"boy.jpg" completionHandler:^(NSError * _Nullable error) {
        if (error) {
            NSLog(@"更換app圖標(biāo)發(fā)生錯(cuò)誤了 : %@",error);
        }
    }];
}

這樣我們就實(shí)現(xiàn)了最簡(jiǎn)單的在App運(yùn)行的時(shí)候更換App圖標(biāo)的方法,但是,當(dāng)點(diǎn)擊按鈕變更圖標(biāo)的時(shí)候,系統(tǒng)會(huì)彈出一個(gè)提示框:

20170509143243781.gif

這體驗(yàn)就不太順滑了,總不能每次都去打斷用戶吧,下面著手解決這個(gè)問(wèn)題。

去掉變更圖標(biāo)時(shí)的提示框

這個(gè)提示框是用 UIAlertController 來(lái)實(shí)現(xiàn)的,而所有的 UIAlertController 都是通過(guò) presentViewController: animated: completion: 方法來(lái)彈出的,我們可以嘗試攔截這個(gè)過(guò)程。但是 UIAlertController 畢竟是一個(gè)很常用的控件,我們不能因此影響到其他的使用。

通過(guò)觀察和測(cè)試可以發(fā)現(xiàn),這個(gè)彈出框是沒(méi)有 title 和 message 的,我們自己做的彈出框一般至少會(huì)有 title,message 也經(jīng)常會(huì)用,所以?xún)烧叨紱](méi)有就比較特殊了,可以根據(jù)這個(gè)情況來(lái)針對(duì)它做特殊處理,禁止彈出。

要攔截系統(tǒng)方法,我們使用runtime中的方法交換技術(shù),實(shí)現(xiàn)一個(gè)自己的 presentViewController: animated: completion: 方法,在自己的方法中,判斷要彈出的 UIAlertController 的 title 和 message 是否都為 nil,是的話就直接返回,也就不會(huì)彈出了;不是的話就正常彈出,這個(gè)我們通過(guò)調(diào)用系統(tǒng)的實(shí)現(xiàn)就可以了。注意我們交換方法時(shí)交換的僅僅是方法的實(shí)現(xiàn)IMP,所以交換之后,我們?nèi)绻胍僬{(diào)用原本系統(tǒng)的實(shí)現(xiàn),需要調(diào)用的反而是我們自己的方法名SEL:

#import <objc/runtime.h>

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    
    // 利用runtime來(lái)替換展現(xiàn)彈出框的方法
    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(ox_presentViewController:animated:completion:));
        // 交換方法實(shí)現(xiàn)
        method_exchangeImplementations(presentM, presentSwizzlingM);
    });
}

// 自己的替換展示彈出框的方法
- (void)ox_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
    
    if ([viewControllerToPresent isKindOfClass:[UIAlertController class]]) {// 要彈出的是UIAlertController
        // 輸出到控制到可以發(fā)現(xiàn)兩者都是null
        NSLog(@"title : %@",((UIAlertController *)viewControllerToPresent).title);
        NSLog(@"message : %@",((UIAlertController *)viewControllerToPresent).message);
        
        // 換圖標(biāo)時(shí)的提示框的title和message都是nil,由此可特殊處理
        UIAlertController *alertController = (UIAlertController *)viewControllerToPresent;
        if (alertController.title == nil && alertController.message == nil) {// 是換圖標(biāo)的提示
            return;
        } else {// 其他提示還是正常處理
            [self ox_presentViewController:viewControllerToPresent animated:flag completion:completion];
            return;
        }
    }
    
    // 其他的彈出還是正常處理
    [self ox_presentViewController:viewControllerToPresent animated:flag completion:completion];
}

這樣就可以實(shí)現(xiàn)開(kāi)頭的效果啦,沒(méi)有提示框,縱享絲般順滑:

20170509113129535 (1).gif

結(jié)

這里只是一個(gè)小demo,實(shí)際要使用的話其實(shí)會(huì)很有意思,不過(guò)更適合那些想要利用圖標(biāo)當(dāng)做窗口展示內(nèi)容的App,或者是特殊時(shí)期在圖標(biāo)上加上活動(dòng)標(biāo)識(shí),雙十一啊之類(lèi)的。

而到此我們做出的變化還僅僅都是利用本地的圖片,在一開(kāi)始就設(shè)置好 Info.plist,但如果想要在發(fā)布之后隨時(shí)下載圖片去替換圖標(biāo),就要相對(duì)麻煩一些,不過(guò)也還好,只需要在下載圖片后改變 Info.plist 的內(nèi)容,增加此圖片的鍵值對(duì),就可以相應(yīng)作出真正的動(dòng)態(tài)變化了。

但是想想iPhone自帶的時(shí)鐘App,可以做到秒鐘都隨著時(shí)間變化,這又是如何做到的呢?這說(shuō)明這種變化圖標(biāo)的方法早就有了,只是一直沒(méi)開(kāi)放,而且可能與現(xiàn)在開(kāi)放出的方式還不太一樣,有興趣可以研究一下當(dāng)前幾個(gè)API背后的實(shí)現(xiàn)原理,然后想想時(shí)鐘的效果又該怎么去做。


示例工程:https://github.com/Cloudox/OXChangeAppIconDemo


查看作者首頁(yè)

參考:http://daiyi.pro/2017/05/01/ChangeYourAppIcons1/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容