iOS動(dòng)態(tài)替換應(yīng)用圖標(biāo)

前言

開發(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)

配置文件(Info.plist).png

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è),支持不同圖片格式。

  1. 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文件,可以看到如下:
主圖標(biāo)的配置方式.png

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è)置頁面

切換效果.png

二、去除彈框

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

彈框分析.png

可以看到,它是一個(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/

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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