切換語言為:簡體

IOS 實現輕鬆更換 App 圖示,無需系統彈窗!

  • 爱糖宝
  • 2024-06-27
  • 2105
  • 0
  • 0

前言

之前在文章中介紹過如何在 App 中設定不同的 Logo,App 的圖示就是我們的門面,所以好看的 Logo 非常重要。在一些場景下,使用者可能會想設定不同的 Logo,比如使用者可以在某些節日(比如春節)設定特別的 Logo,另外還可以在 App 內為高階使用者定製不同的 Logo。

不知道大家是否還記得,使用系統方法設定不同 Logo 的時候會有一個系統彈窗:

IOS 實現輕鬆更換 App 圖示,無需系統彈窗!

那麼如何避免這個彈窗的彈出呢?今天就來講講這個技術點。

如何避免系統彈窗?

系統的 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 設定成功")
        }
    }


這種反射的方法相對來說不是很保險,一方面是過審問題,現在能過審不代表永遠能過審,另一方面系統的私有方法隨著系統的迭代可能會發生變化,到時候就會發生崩潰或者無效等問題,因此優先推薦第一種方法來設定。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.