前言
之前在文章中介紹過如何在 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 設定成功") } }
這種反射的方法相對來說不是很保險,一方面是過審問題,現在能過審不代表永遠能過審,另一方面系統的私有方法隨著系統的迭代可能會發生變化,到時候就會發生崩潰或者無效等問題,因此優先推薦第一種方法來設定。