dailyLearning -- 動態(tài)更換 App 圖標(biāo)

前言

動態(tài)更換App圖標(biāo)這件事,在用戶里總是存在需求的:有些用戶喜歡“美化”自己的手機(jī)帝火。至于用戶們喜歡美化到什么程度秘蛔,這得看個(gè)人需求。有的用戶想定制個(gè)性的App圖標(biāo)岁忘,那么各大iPhone論壇里都有方法可以不越獄更改App圖標(biāo)辛慰;有的用戶想讓App圖標(biāo)“動”起來(如系統(tǒng)應(yīng)用時(shí)鐘)踊沸,那么不越個(gè)獄還真不好辦瓷翻。
不過今天我們想談?wù)勌O果官方對于動態(tài)更換App圖標(biāo)的支持。

Demo演示:

DynamicAppIconDemo1.gif

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

該功能應(yīng)用的場景

  • 1遮糖、白天/夜間模式切換麻汰,在切換App主色調(diào)同時(shí)切換App圖標(biāo)速客。

  • 2、各類皮膚主題(淘寶就可換膚)五鲫,附帶App圖標(biāo)一塊更換溺职。

  • 3、利用App圖標(biāo)表達(dá)某種特定功能位喂,如Demo中的浪耘,提示當(dāng)前天氣。

  • 4塑崖、圖標(biāo)促銷提示七冲,如淘寶京東特定節(jié)日:11.11、6.18弃舒,提前更換App圖標(biāo)。

當(dāng)然該功能(API)當(dāng)前只支持iOS10.3以上的系統(tǒng),所以只能當(dāng)做一項(xiàng)附加功能來進(jìn)行使用聋呢。下面將詳細(xì)講解下如何使用代碼來實(shí)現(xiàn)此功能苗踪。

API與文檔

API方法

@interface UIApplication (UIAlternateApplicationIcons)
// 如果為NO,表示當(dāng)前進(jìn)程不支持替換圖標(biāo)
@property (readonly, nonatomic) BOOL supportsAlternateIcons NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2));
// 傳入nil代表使用主圖標(biāo). 完成后的操作將會在任意的后臺隊(duì)列中異步執(zhí)行; 如果需要更改UI削锰,請確保在主隊(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));
// 如果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));
@end

總共3個(gè)方法,簡潔明了器贩,不過但看這3個(gè)API颅夺,我們并不清楚alternateIconName是如何與app圖標(biāo)掛鉤的,所以我們需要進(jìn)一步翻看文檔!

文檔

shift+command+0打開文檔蛹稍,依次查看3個(gè)API吧黄,翻譯如下:

1.supportsAlternateIcons



(翻譯)只有系統(tǒng)允許改變你的app圖標(biāo)時(shí)該值才為YES。你需要在Info.plist文件中的CFBundleIcons這個(gè)鍵內(nèi)聲明可更換的app圖標(biāo)

2.alternateIconName



(翻譯)當(dāng)系統(tǒng)展示的是你更換后的app圖標(biāo)時(shí)唆姐,該值即為圖標(biāo)名字(Info.plist中定義的圖標(biāo)名字)拗慨。如果展示的是主圖標(biāo)時(shí),這個(gè)值為nil奉芦。

3.setAlternateIconName:completionHandler:



(翻譯)alertnateIconName參數(shù):該參數(shù)為需要更換的app圖標(biāo)名字赵抢,是在你的Info.plist中的CFBundleAlertnateIcons鍵里定義的。如果你想顯示的是用CFBundlePrimaryIcon鍵所定義的主圖標(biāo)的話声功,就傳入nil烦却。CFBundleAlertnateIcons與CFBundlePrimaryIcon鍵都是在CFBundleIcons里面定義的。

(翻譯)completionHandler參數(shù):該參數(shù)用來處理(更換)結(jié)果先巴。當(dāng)系統(tǒng)嘗試更改app的圖標(biāo)后其爵,會將結(jié)果數(shù)據(jù)通過該參數(shù)傳入并執(zhí)行(該執(zhí)行過程是在UIKit所提供的隊(duì)列執(zhí)行,并非主隊(duì)列)筹裕。該執(zhí)行過程會攜帶一個(gè)參數(shù):error醋闭。如果更換app圖標(biāo)成功,那么這個(gè)參數(shù)就是nil朝卒。如果更換過程中發(fā)生了錯(cuò)誤证逻,那么該對象會指明錯(cuò)誤信息,并且app的圖標(biāo)保持不變抗斤。


(翻譯)使用該方法改變app圖標(biāo)為主圖標(biāo)或者可更換的圖標(biāo)囚企。只有在supportsAlternateIcons

的返回值為YES時(shí)才能更換。

(翻譯)你必須在Info.plist文件的CFBundleIcons鍵里面聲明可以更換的app圖標(biāo)(主圖標(biāo)和可更換圖標(biāo))瑞眼。如果需要獲取關(guān)于可更換圖標(biāo)的配置信息龙宏,請查閱 Information Property List Key Reference 里面有關(guān)CFBundleIcons的描述。

  • 文檔中反復(fù)提到了Info.plist文件與CFBundleIcons伤疙,這是Xcode6之前是用來配置App圖標(biāo)的老方法银酗,后來有了更完備的Assets.scassets辆影,配置App圖標(biāo)更簡單與完善了。不過如今該方法再次被搬上臺面黍特,在蘋果內(nèi)部一定也是歷經(jīng)多次“撕逼”后的結(jié)果蛙讥,為何蘋果急于在10.3而不是11推出該API?為何蘋果不使用Assets.scassets配置可變更的App圖標(biāo)灭衷?我們不得而知次慢,不過相信蘋果后期會對該配置方法做優(yōu)化的。

可變更App圖標(biāo)的配置方法

官方配置文檔


**該配置文檔的內(nèi)容較多翔曲,我們挑重點(diǎn)羅列下(忽略tvOS部分迫像,下同):

Info.plist是個(gè)字典,假設(shè)為NSDictionary *infoPlist瞳遍。
CFBundleIcons是Info.plist字典里的一個(gè)鍵@"CFBundleIcons"闻妓。
CFBundleIcons對應(yīng)的value是個(gè)字典。
CFBundleIcons里面能夠包含的鍵有:CFBundlePrimaryIcon傅蹂、CFBundleAlternateIcons纷闺、UINewsstandIcon。

讓我們用代碼展示下這個(gè)繞口的結(jié)構(gòu):

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



這是關(guān)于CFBundleAlternateIcons的配置文檔:

其中有一句話份蝴,不仔細(xì)思考很難明

In iOS, the value of the key is a dictionary. The key for each dictionary entry is the name of the alternate icon
翻譯:該鍵對應(yīng)的值是字典犁功,每個(gè)字典條目的鍵都是備用圖標(biāo)的名稱。

從這句話中無法很快理清CFBundleAlternateIcons下層的數(shù)據(jù)結(jié)構(gòu)婚夫。實(shí)際上這句話表達(dá)的意思是:

  • 該鍵對應(yīng)的值是字典浸卦,這個(gè)字典里的每一個(gè)鍵對應(yīng)的又是一個(gè)個(gè)字典,而這些鍵都是備用圖標(biāo)的名稱案糙。

讓我們把剩余的重點(diǎn)羅列下:

  • CFBundleAlternateIcons所對應(yīng)的value是個(gè)字典(iOS中)限嫌,假設(shè)為NSDictionary * alertnateIconsDic。

alertnateIconsDic的鍵时捌,都是備用圖標(biāo)的名字怒医,假設(shè)為@"newAppIcon"和@"newAppIcon2"。 @"newAppIcon"的value是個(gè)包含CFBundleIconFiles和UIPrerenderedIcon這兩個(gè)鍵的字典奢讨。

  • CFBundleIconFiles的value是字符串或者數(shù)組(數(shù)組內(nèi)容也為字符串)稚叹。字符串的內(nèi)容為各尺寸備用圖標(biāo)的名字。

  • UIPrerenderedIcon的value是BOOL值拿诸。這個(gè)鍵值所代表的作用在iOS7之后(含iOS7)已失效扒袖,在iOS6中可渲染app圖標(biāo)為帶高亮效果。所以這個(gè)值目前我們可以不用關(guān)心亩码。

讓我們用代碼展示下CFBundleAlternateIconsvalue的結(jié)構(gòu):

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

實(shí)際配置文件(Info.plist)

對照著上述的配置文檔季率,我們實(shí)際配置完的Info.plist是這樣子的:


![](https://upload-images.jianshu.io/upload_images/2790607-4a6b0bd45694cc62.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

當(dāng)然也要拖入對應(yīng)的App圖標(biāo):



不過這里我們好像還少配置了App主圖標(biāo),也就是正常情況下我們的圖標(biāo)描沟。按照文檔所說飒泻,我們需要在CFBundleIcons里面配置CFBundlePrimaryIcon這個(gè)主圖標(biāo)對應(yīng)的內(nèi)容鞭光,但是實(shí)際上,我們還是按照老方法泞遗,在Assets.xcassets中配置AppIcon衰猛,對應(yīng)尺寸填上對應(yīng)圖片即可。為什么這樣子就可以配置主圖標(biāo)呢刹孔?讓我們來看看某知名電商的ipa(在AppStore上下載的包)內(nèi)的Info.plist(位于Payload/XXXXXX/Info.plist):

當(dāng)然你也可以在你自己App打出的包內(nèi)進(jìn)行查看,系統(tǒng)其實(shí)是會將Assets.xcassets中配置的AppIcon轉(zhuǎn)化為Info.plist中的CFBundlePrimaryIcon娜睛。所以我們主圖標(biāo)的配置方式還是與原先一樣髓霞。

其他注意事項(xiàng):

文件擴(kuò)展名,如@2x,@3x畦戒,要么統(tǒng)一不寫方库,那么系統(tǒng)會自動尋找合適的尺寸。要寫就需要把每張icon的擴(kuò)展名寫上障斋,和上圖的格式一樣纵潦,在本系列文章的Demo中也有一個(gè)單獨(dú)的Demo示例如何添加多尺寸icon。

iPad版本如果需要有更換的圖標(biāo)垃环,需要在CFBundleIcons?ipad同樣設(shè)置一次

更換圖標(biāo)后邀层,如何驗(yàn)證iPhone上使用了多尺寸的圖標(biāo)?


打開DynamicAppIcon(帶尺寸)這個(gè)Demo遂庄。該Demo中寥院,我們在各個(gè)尺寸的圖標(biāo)右上角打個(gè)”標(biāo)記“,然后使用上文介紹的setAlternateIconName:completionHandler:

進(jìn)行圖標(biāo)更換涛目。更換圖標(biāo)的同時(shí)秸谢,我們再做一件事:

// 測試推送上是否使用了20尺寸的圖標(biāo)
UILocalNotification *noti = [[UILocalNotification alloc] init];
noti.fireDate = [NSDate dateWithTimeIntervalSinceNow:5];
noti.alertBody = @"我們看看推送上面的App圖標(biāo)";
[[UIApplication sharedApplication] scheduleLocalNotification:noti];

這里我們發(fā)送了一個(gè)本地通知,一會我們就能看到通知上顯示的是什么圖標(biāo)了:



看到圖標(biāo)的區(qū)別霹肝,也就說明了我們在Info.plist里面設(shè)置的多尺寸圖標(biāo)生效了:


第二部分:[無彈框更換App圖標(biāo)]

什么是彈框

讓我們查看彈框的本質(zhì)

彈框與UIAlertController長的倒是挺像的估蹄。讓我們來剖析下這個(gè)彈框:

可以看到彈框就是私有類_UIAlertControllerView,讓我們再對比下系統(tǒng)的UIAlertController:



所以更換App時(shí)的彈框就是UIAlertController沫换,只不過上面的控件不太一樣罷了臭蚁。(其實(shí)我們也能做到在UIAlertController上添加任意控件)

攔截彈框

既然知道了彈框是UIAlertController,那么我們自然而然想到苗沧,該彈框是由ViewController通過presentViewController:animated:completion:方法彈出刊棕。那么我們就可以通過Method swizzling hook該彈框,不讓其進(jìn)行彈出即可:

#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:));
        // 交換方法實(shí)現(xiàn)
        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的特征甥角,我們可以發(fā)現(xiàn),更換App圖標(biāo)時(shí)的彈框是沒有title與message的识樱,但是我們一般使用的UIAlertController都是帶title嗤无、message的震束,畢竟不會彈個(gè)空白的框給用戶玩。

所以該方法中通過判斷title與message來捕捉更換App圖標(biāo)時(shí)的彈框当犯,并直接return即可垢村。

總結(jié)

  • 其實(shí)關(guān)于界面上的東西,利用動態(tài)特性沒有什么是不能做的嚎卫,蘋果既然公開了動態(tài)API嘉栓,我們就可以通過動態(tài)方法去了解甚至改造我們想要的東西,如系統(tǒng)控件如何實(shí)現(xiàn)等拓诸。蘋果的”規(guī)范“在應(yīng)用層面其實(shí)是無法阻擋開發(fā)者步伐的侵佃,當(dāng)然動態(tài)特性也不能夠?yàn)E用(如私有方法),畢竟審核人員才是爸爸!
  • 盡管目前實(shí)現(xiàn)了在用戶無感的情況下替換App圖標(biāo)奠支,但是可替換的圖標(biāo)還是必須預(yù)先放入工程中馋辈,并且要在Info.plist內(nèi)指定。這很大程度上限制了更換圖標(biāo)的動態(tài)性:比如我們某天想要推出一款新主題以及對應(yīng)的App圖標(biāo)倍谜,但是新的App圖標(biāo)并沒有預(yù)先放入工程的main bundle中迈螟,也沒有在Info.plist中進(jìn)行指定,所以我們在不上架新版本的情況下尔崔,無法推出該新App圖標(biāo)

原文鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末答毫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子季春,更是在濱河造成了極大的恐慌烙常,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鹤盒,死亡現(xiàn)場離奇詭異蚕脏,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)侦锯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門驼鞭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人尺碰,你說我怎么就攤上這事挣棕。” “怎么了亲桥?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵洛心,是天一觀的道長。 經(jīng)常有香客問我题篷,道長词身,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任番枚,我火速辦了婚禮法严,結(jié)果婚禮上损敷,老公的妹妹穿的比我還像新娘。我一直安慰自己深啤,他們只是感情好拗馒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著溯街,像睡著了一般诱桂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上呈昔,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天访诱,我揣著相機(jī)與錄音,去河邊找鬼韩肝。 笑死,一個(gè)胖子當(dāng)著我的面吹牛九榔,可吹牛的內(nèi)容都是我干的哀峻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼哲泊,長吁一口氣:“原來是場噩夢啊……” “哼剩蟀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起切威,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤育特,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后先朦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缰冤,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年喳魏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了棉浸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡刺彩,死狀恐怖迷郑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情创倔,我是刑警寧澤嗡害,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站畦攘,受9級特大地震影響霸妹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜知押,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一抑堡、第九天 我趴在偏房一處隱蔽的房頂上張望摆出。 院中可真熱鬧,春花似錦首妖、人聲如沸偎漫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽象踊。三九已至,卻和暖如春棚壁,著一層夾襖步出監(jiān)牢的瞬間杯矩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工袖外, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留史隆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓曼验,卻偏偏與公主長得像泌射,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子鬓照,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 幾世輪回的姻緣熔酷,風(fēng)沙觸摸著青春的臉。 騎馬而歸的少年豺裆,鋪滿殘紅戈壁的諾言拒秘。 你為誰?青絲及腰兩千年臭猜。 誰為你躺酒?微笑...
    完顏暉閱讀 716評論 0 8
  • 起步 上車調(diào)整座位與靠背,后視鏡蔑歌。 繞車一周阴颖,上車系安全帶。 點(diǎn)火(注意檔位在空擋) 進(jìn)行模擬夜間燈光操作a. 打...
    petreling閱讀 4,059評論 0 2
  • 目錄 這篇文章記錄了在centos-6.5上安裝zabbix-3.0.4的過程丐膝。 安裝環(huán)境 系統(tǒng)環(huán)境:CentOS...
    skyline__閱讀 691評論 0 3
  • 最近實(shí)在太奇怪了量愧,我懷疑我得了健忘癥,我明明記得上周末我把半個(gè)沒吃完的西瓜放在了冰箱里帅矗,可是這周回來打開冰箱卻不見...
    王魚洋閱讀 898評論 1 51