前言
開發(fā)過程需要打測試環(huán)境的包給測試使用,之前是用不同的target做區(qū)分的,但是這種方式切換環(huán)境時(shí)還需要重新安裝包,十分的不方便。于是老板要求使用同意一個(gè)包,動(dòng)態(tài)切換環(huán)境,為了區(qū)分不同環(huán)境的包,最好改變一下App圖標(biāo),方便測試區(qū)分,于是就研究了一下動(dòng)態(tài)切換icon的方法。
Demo下載:https://github.com/ZhangJingHao/ZJHChangeIconDemo.git
一、基礎(chǔ)使用
1、API方法介紹
在iOS 10.3之后,蘋果官方提供了相關(guān)的API來實(shí)現(xiàn)這個(gè)功能,主要是下面這幾個(gè)方法:
@interface UIApplication (UIAlternateApplicationIcons)
// 如果為false,當(dāng)前進(jìn)程不支持備用圖標(biāo)。
@property (readonly, nonatomic) BOOL supportsAlternateIcons;
// 傳遞' nil '來使用主應(yīng)用程序圖標(biāo)。完成處理程序?qū)⒃谌我夂笈_(tái)隊(duì)列上異步調(diào)用;在執(zhí)行任何進(jìn)一步的UI工作之前,請(qǐng)確保分派回主隊(duì)列
- (void)setAlternateIconName:(nullable NSString *)alternateIconName completionHandler:(nullable void (^)(NSError *_Nullable error))completionHandler;
// 如果為“nil”,表示正在使用主應(yīng)用程序圖標(biāo)。
@property (nullable, readonly, nonatomic) NSString *alternateIconName;
@end
一共就三個(gè)方法,簡單明了。第一個(gè)判斷系統(tǒng)是否支持,第二個(gè)更改圖標(biāo),第三個(gè)獲取正在使用的圖標(biāo)名稱。因更改的是外部系統(tǒng)調(diào)用的圖標(biāo),所以還需要把圖標(biāo)資源事先配置好,是不能隨意修改的。
2、配置文件(Info.plist)

1)把圖icon片添加到項(xiàng)目中,動(dòng)態(tài)修改的icon不能放在Assets.xcassets里,正常的主icon還是可以放在這里設(shè)置的。
2)在Info.plist中添加Icon file (iOS 5)一列。
3)在Icon file (iOS 5)內(nèi)添加一個(gè)Key:CFBundleAlternateIcons,類型為字典,在這個(gè)字典里配置我們所有需要?jiǎng)討B(tài)修改的icon:鍵為icon的名稱,值為一個(gè)字典。如“DEV”、“PRO”
4)這個(gè)字典里包含兩個(gè)鍵:CFBundleIconFiles,其值類型為Array,內(nèi)容為icon的名稱,如“DEVIcon20x20”,這里可以填多個(gè),支持不同圖片格式。
- UIPrerenderedIcon的value是BOOL值。這個(gè)鍵值所代表的作用在iOS7之后(含iOS7)已失效,在iOS6中可渲染app圖標(biāo)為帶高亮效果。所以這個(gè)值目前我們可以不用關(guān)心。
6)系統(tǒng)會(huì)將Assets.xcassets中配置的AppIcon轉(zhuǎn)化為Info.plist中的CFBundlePrimaryIcon。所以我們主圖標(biāo)的配置方式還是與原先一樣。從編譯后的工程中查看info.plist文件,可以看到如下:

3、代碼實(shí)現(xiàn)
/// 切換系統(tǒng)icon
- (void)changeIcon:(UIButton *)btn {
if (@available(iOS 10.3, *)) {
// 判斷系統(tǒng)是否支持
if (![[UIApplication sharedApplication] supportsAlternateIcons]) {
return;
}
}
if (@available(iOS 10.3, *)) {
// 設(shè)備指定圖片,iconName為info.plist中的key
[[UIApplication sharedApplication] setAlternateIconName:@"PRO" completionHandler:^(NSError * error) {
if (error) {
NSLog(@"更換app圖標(biāo)發(fā)生錯(cuò)誤了 : %@",error);
}
}];
}
}
4、效果查看
圖片分別是:彈框提示、app主屏幕、通知頁、設(shè)置頁面

二、去除彈框
切換圖標(biāo)時(shí),系統(tǒng)默認(rèn)是要彈出提示框的,還需要用戶手動(dòng)點(diǎn)擊“OK”,體驗(yàn)不是很好。所以需要想辦法把彈框去掉。

可以看到,它是一個(gè):UIAlertController,然后又包含了私有類:_UIAlternateApplicationIconsAlertContentViewController。
既然知道了彈框是UIAlertController,那么我們自然而然想到,該彈框是由ViewController通過presentViewController:animated:completion:方法彈出。那么我們就可以通過Method swizzling hook該彈框,不讓其進(jìn)行彈出即可。注意:因hook的是系統(tǒng)方法,可能會(huì)引起其他問題,還是慎用吧。
@implementation UIViewController (CustomPresent)
+ (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(zjh_presentViewController:animated:completion:));
method_exchangeImplementations(presentM, presentSwizzlingM);
});
}
- (void)zjh_presentViewController:(UIViewController *)viewControllerToPresent
animated:(BOOL)flag
completion:(void (^)(void))completion {
if ([viewControllerToPresent isKindOfClass:[UIAlertController class]]) {
UIAlertController *alertController =
(UIAlertController *)viewControllerToPresent;
// 判斷標(biāo)題title、內(nèi)容message是否為空
if (alertController.title == nil && alertController.message == nil) {
id conVC = [alertController valueForKey:@"_contentViewController"];
if (conVC) {
/* 判斷_contentViewController的值是否為
_UIAlternateApplicationIconsAlertContentViewController */
NSString *clsStr = NSStringFromClass([conVC class]);
NSString *temStr = @"_UIAlternateApplicationIconsAlertContentViewController";
if ([clsStr isEqualToString:temStr]) {
if (completion) {
completion();
}
return;
}
}
} else {
[self zjh_presentViewController:viewControllerToPresent
animated:flag
completion:completion];
return;
}
}
[self zjh_presentViewController:viewControllerToPresent
animated:flag
completion:completion];
}
三、圖片自動(dòng)添加水印
本想動(dòng)態(tài)配置任意類型的圖標(biāo)的,但是好像無法實(shí)現(xiàn)。因?yàn)閳D片資源是打包放在ipa里的,安裝到系統(tǒng)后,圖片資源是沒有權(quán)限動(dòng)態(tài)替換的,除非是越獄手機(jī)。所以就無法更換了,只能事先集成的安裝包里。于是就寫了方法,自動(dòng)添加水印。大致流程是:先讀取添加三角形水印,然后再貼個(gè)文本,之后截圖保存一下就可以了。核心代碼如下,詳情可參看demo
// 處理圖片
- (void)dealIconWithImg:(UIImage *)img
iconName:(NSString *)iconName
imgWH:(CGFloat)imgWH
labText:(NSString *)labText {
// 還原視圖
[self.iconView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
[self.iconView.layer.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
// 設(shè)置框的大小和圖片大小一致
self.iconView.frame = CGRectMake(50, 50, imgWH, imgWH);
// 設(shè)置圖片
self.iconView.image = img;
// 圖片靠左還是靠右
BOOL isLeft = NO;
// 添加背景三角形
CGFloat triangleH = self.iconView.frame.size.width * 0.4;
[self addTriangleWithInView:self.iconView height:triangleH isLeft:isLeft];
// 添加文字
// 已知直角三角形兩個(gè)直角邊長度,求斜邊長度
CGFloat labW = hypot(triangleH, triangleH);
CGFloat rate = 0.3; // 文字高度比例
CGFloat labH = labW * rate;
// 下面是一道數(shù)學(xué),拿著紙筆算出來的方法
CGFloat labX = -triangleH * 0.5 * (sqrt(2) - (1-rate));
CGFloat labY = triangleH * 0.5 * ( (1-rate) - sqrt(2) * rate);
if (!isLeft) {
CGFloat cX = labX + 0.5 * labW;
CGFloat cY = labY + 0.5 * labH;
cX = self.iconView.frame.size.width - cX;
labX = cX - 0.5 * labW;
labY = cY - 0.5 * labH;
}
CGRect labF = CGRectMake(labX, labY, labW, labH);
UILabel *lab = [[UILabel alloc] initWithFrame:labF];
lab.text = labText;
lab.textColor = [UIColor whiteColor];
lab.textAlignment = NSTextAlignmentCenter;
CGFloat fontSize = labH * 0.85; // 文字大小
lab.font = [UIFont boldSystemFontOfSize:fontSize];
if (isLeft) {
lab.transform = CGAffineTransformMakeRotation(-M_PI/4);
} else {
lab.transform = CGAffineTransformMakeRotation(M_PI/4);
}
[self.iconView addSubview:lab];
// view截圖
UIGraphicsBeginImageContextWithOptions(self.iconView.frame.size, NO, [UIScreen mainScreen].scale);
[self.iconView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *newImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.iconView.image = newImg;
/// 圖片存儲(chǔ)在沙河目錄
NSData *imgData = UIImagePNGRepresentation(newImg);
NSString *imgPath = [NSString stringWithFormat:@"%@/%@", self.imgFilePath, iconName];
[imgData writeToFile:imgPath atomically:YES];
}
參考鏈接:
iOS動(dòng)態(tài)更換App圖標(biāo):http://daiyi.pro/2017/05/01/ChangeYourAppIcons1/