前言
之前在文章中介绍过如何在 App 中设置不同的 Logo,App 的图标就是我们的门面,所以好看的 Logo 非常重要。在一些场景下,用户可能会想设置不同的 Logo,比如用户可以在某些节日(比如春节)设置特别的 Logo,另外还可以在 App 内为高级用户定制不同的 Logo。
不知道大家是否还记得,使用系统方法设置不同 Logo 的时候会有一个系统弹窗:
那么如何避免这个弹窗的弹出呢?今天就来讲讲这个技术点。
如何避免系统弹窗?
系统的 UIAlertController
是通过一个控制器 present
出来的,那么有一个思路就是写一个透明的 ViewController
,在设置图标时先把这个透明的 VC 弹出来,然后重写 present(_:,:,:)
方法,在其中直接自己 dismiss
掉:
首先是这个透明控制器的实现:
class TransparentViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // 添加一个透明背景视图 let backgroundView = UIView() backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.0) backgroundView.frame = view.bounds view.addSubview(backgroundView) } override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { // 当系统想要调用弹窗时直接 dismiss 掉 dismiss(animated: false) } }
然后,在实现设置图标的相关代码
guard UIApplication.shared.supportsAlternateIcons else { // 不支持 return } let transparentVC = TransparentViewController() transparentVC.modalPresentationStyle = .overFullScreen self.present(transparentVC, animated: false) { UIApplication.shared .setAlternateIconName(selectName) { error in if let error { print("设置 App Icon 出错: \(error)") } else { print("App Icon 设置成功") } } }
这样的话,当图标设置成功,系统想通过当前控制器调用 present
方法弹出系统弹窗时,我们直接把当前控制器 dismiss
掉,这样就不会有系统弹窗了。
有没有更简单的方法
我查了一些资料,有个方法更简单,就是利用私有方法,当系统调用 setAlternateIconName
方法设置图标时,底层实际上调用了一个 _setAlternateIconName
的私有方法,我们只需要通过反射或者重新实现这个方法来调用即可。
1、反射
func setApplicationIconName(_ iconName: String?) { if UIApplication.shared.responds(to: #selector(getter: UIApplication.supportsAlternateIcons)) && UIApplication.shared.supportsAlternateIcons { typealias setAlternateIconName = @convention(c) (NSObject, Selector, NSString?, @escaping (NSError) -> ()) -> () let selectorString = "_setAlternateIconName:completionHandler:" let selector = NSSelectorFromString(selectorString) let imp = UIApplication.shared.method(for: selector) let method = unsafeBitCast(imp, to: setAlternateIconName.self) method(UIApplication.shared, selector, iconName as NSString?, { error in if let error { print("设置 App Icon 出错: \(error)") } else { print("App Icon 设置成功") } }) } }
这种方式是通过字符串反射 _setAlternateIconName:completionHandler:
方法来实现的,这种方式也有网友经过实验已经通过了审核,所以不必担心过审问题。
2、定义这个私有方法
因为不使用反射的话我们是无法调用这个私有函数的,因为未声明,另一个思路是使用 OC 的头文件来声明这个方法,这样我们就可以直接调用了,创建一个 OC 的头文件,给系统的 UIApplication
写一个分类,来声明这个方法:
#import <UIKit/UIKit.h> @import UIKit; @interface UIApplication (UIApplication_Private) - (void)_setAlternateIconName:(nullable NSString *)alternateIconName completionHandler:(nullable void (^)(NSError *_Nullable error))completionHandler; @end
这样,我们只需要在系统方法前增加一个下划线来调用就行了。
UIApplication.shared ._setAlternateIconName(selectName) { error in if let error { print("设置 App Icon 出错: \(error)") } else { print("App Icon 设置成功") } }
这种反射的方法相对来说不是很保险,一方面是过审问题,现在能过审不代表永远能过审,另一方面系统的私有方法随着系统的迭代可能会发生变化,到时候就会发生崩溃或者无效等问题,因此优先推荐第一种方法来设置。