iOS 动态Icon 静默切换

PK创意闹新春,我正在参加「春节创意投稿大赛」,详情请看:春节创意投稿大赛

起因

一年一度的春节就要来了,各大厂商APP都已经把Icon更换为春节图片,奇怪的是,APP没有更新怎么Logo会变?今天就来说说这个技术点(淘宝、支付宝已经用了几年了)。

需求

  • 修改App内Icon
  • 静默切换

系统API

首先,这个技术仅在 iOS 10.3以上才可用

先展示系统API

@available(iOS 10.3, *)
open var supportsAlternateIcons: Bool { get }//是否支持备用logo
  
// Pass `nil` to use the primary application icon. The completion handler will be invoked asynchronously on an arbitrary background queue; be sure to dispatch back to the main queue before doing any further UI work.
@available(iOS 10.3, *)
open func setAlternateIconName(_ alternateIconName: String?, completionHandler: ((Error?) -> Void)? = nil)//设置备用logo
  
// If `nil`, the primary application icon is being used.
@available(iOS 10.3, *)
open var alternateIconName: String? { get }//当前logo名称

网上找到的实现方式

网上查了下资料,先说下网上的实现步骤:

1、添加icon图片

图片放在工程目录下,而不是系统的资源管理Assets文件下

资源.png

2、info.plist配置

在info.plist中添加关键字:Icon files(iOS 5)

关键字.png 添加成功后是这个样子

字段样子.png

似乎没有网上说的CFBundleAlternateIcons关键字

扫描二维码关注公众号,回复: 14484547 查看本文章

没有那就手动加吧,按照帖子加上之后的样子

加上关键字后的样子.png

可以看到上面的item对应的就是icon 的名字,这里可以添加多个,以备各种设备的适配

多个图片的样子.png 这样配置就完成了,注意记住对应的名称

3、调用代码

上代码

if UIApplication.shared.supportsAlternateIcons{
       let str = UIApplication.shared.alternateIconName
       if str != "sunshine"{
           UIApplication.shared.setAlternateIconName("sunshine") { error in
               if error != nil{
                    print("\(String(describing: error))")
               }
            }
        }
 }

注意:sunshine对应上面配置的名称

4、网上资料实现方式总结

到这里就可以看到app logo被替换了,会一个弹窗提示,这个后面说,先说这个流程有没有觉得很麻烦呀,而且资源管理放在工程目录下,作为以优雅著称的苹果会容忍这样的问题,本着探索的精神,看了下工程配置

更优雅的实现方式

在Build Settings 下找到了Alternate App Icon Sets 配置项,这不就是备用logo集合嘛,接下来开始为的表演

1、现在工程资源文件夹下新建两个AppIcon 资源夹,分别命名为sat 和 gua,然后按照配置常用logo的方式,添加资源

2、在Build Settings 下的Alternate App Icon Sets项右边双击,添加sat gua两项

3、使用上面的设置代码

OK,至此完美收工。很简单有木有!!!!

弹窗问题处理

但是大家在使用时,肯定也发现了问题,这样设置会有一个提示弹窗,没有淘宝、支付宝那么优雅,本着追求极致的态度,我们继续研究,发现弹窗就是UIAlertController,并且是没有title和message的

这个时候就要使用魔法了Runtime登场

Swift语言弹窗处理代码

直接上代码:Swift

extension UIViewController{
    public class func initializeMethod(){
        if self != UIViewController.self{
            return
        }
        DispatchQueue.once(token: "ChangeIcon") {
            let orignal = class_getInstanceMethod(self, #selector(UIViewController.present(_:animated:completion:)))
            let swizzling = class_getInstanceMethod(self, #selector(UIViewController.ssl_present(_:animated:completion:)))
            if let old = orignal, let new = swizzling{
                method_exchangeImplementations(old, new)
            }
        }
    }
    @objc private func ssl_present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
        if viewControllerToPresent is UIAlertController{
            let vc = viewControllerToPresent as! UIAlertController
            if vc.title == nil && vc.message == nil{
                return
            }
        }
        self.ssl_present(viewControllerToPresent, animated: flag, completion: completion)
    }
}
extension DispatchQueue{
    private static var _onceTracker = [String]()
    public class func once(token: String, block:()->()){
        objc_sync_enter(self)
        defer{
            objc_sync_exit(self)
        }
        if _onceTracker.contains(token){
            return
        }
        _onceTracker.append(token)
        block()
    }
}

启用:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

  UIViewController.initializeMethod()

  return true
}

OC语言弹窗处理代码

不忘老朋友,网上找的ObjectC 代码参考地址

// UIViewController+LQNoPresent.h
#import <UIKit/UIKit.h>
@interface UIViewController (LQNoPresent)
@end
#import "UIViewController+LQNoPresent.h"
#import <objc/runtime.h>
@implementation UIViewController (LQNoPresent)
+ (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(lq_presentViewController:animated:completion:));
        method_exchangeImplementations(presentM, presentSwizzlingM);
    });
}
- (void)lq_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;
        }
    }
    [self lq_presentViewController:viewControllerToPresent animated:flag completion:completion];
}
@end

到这里就结束了,需求完美结束,如有不足,欢迎大家指正!!!

本文Swift参考:iOS动态更换Icon的全过程记录

猜你喜欢

转载自juejin.im/post/7049622522240729096