iOS神技之動態更換APP的Icon圖

weixin_33809981發表於2018-03-16
  • 在iOS10.3系統釋出之前, 眾所周知, 在App Store上架的APP如果要更換Icon圖, 只能更新版本替換;
  • 這次蘋果卻在iOS10.3系統中加入了了更換應用圖示的新功能,當應用安裝後,開發者可以為應用提供多個應用圖示選擇。
  • 使用者可以自由的在這些圖示之間切換,並及時生效。
  • 這是因為 10.3 裡引入了一個新的 API,它允許在 App 執行的時候,通過程式碼為 app 更換 icon

一. 專案配置

  • 雖然提供了更換的功能,但更換的 icon 是有限制的

  • 它只能更換專案中提前新增配置好的Icon圖

  • 具體可參考demo--github專案地址

  • 這裡先看個效果

1. 備選Icon

  • 首先你需要將備選的Icon圖新增到專案中,
  • 注意:
    • 圖片不要放到Assets.xcassets, 而應該直接放到工程中, 不然可能導致更換Icon時, 找不到圖片, 更換失敗
    • info.plist 的配置中,圖片的檔名應該儘量不帶 @2x/@3x 字尾副檔名,而讓它自動選擇

2. 配置info.plist檔案

  • info.plist檔案中,新增對應的CFBundleAlternateIcons的資訊
  • 這裡也可以檢視官方的相關介紹

Source Code新增方式如下

        <key>CFBundleAlternateIcons</key>
		<dict>
			<key>天天特價</key>
			<dict>
				<key>CFBundleIconFiles</key>
				<array>
					<string>天天特價</string>
				</array>
				<key>UIPrerenderedIcon</key>
				<false/>
			</dict>
			<key>小房子</key>
			<dict>
				<key>CFBundleIconFiles</key>
				<array>
					<string>小房子</string>
				</array>
				<key>UIPrerenderedIcon</key>
				<false/>
			</dict>
			<key>小貓</key>
			<dict>
				<key>CFBundleIconFiles</key>
				<array>
					<string>小貓</string>
				</array>
				<key>UIPrerenderedIcon</key>
				<false/>
			</dict>
			<key>郵件資訊</key>
			<dict>
				<key>CFBundleIconFiles</key>
				<array>
					<string>郵件資訊</string>
				</array>
				<key>UIPrerenderedIcon</key>
				<false/>
			</dict>
		</dict>

複製程式碼
  • 注意事項:
    • 雖然文件中寫著「You must declare your app's primary and alternate icons using the CFBundleIcons key of your app's Info.plist file. 」,但經測試,CFBundlePrimaryIcon 可以省略掉。在工程配置 App Icons and Launch Image - App Icons Source 中使用 asset catalog(預設配置),刪除 CFBundlePrimaryIcon 的配置也是沒有問題的。
    • 省略這個配置的好處是,避免處理 App icon 的尺寸。現在的工程中,大家一般都使用 asset catalog 進行 icon 的配置,而一個 icon 對應有很多尺寸的檔案。省略 CFBundlePrimaryIcon 就可以沿用 Asset 中的配置。
    • 如果想設定回預設 icon,在 setAlternateIconName 中傳入 nil 即可

二. API呼叫

下面我們看一下系統提供的三個API, 這裡產看官方文件

var supportsAlternateIcons: Bool
//一個布林值,指示是否允許應用程式更改其圖示

var alternateIconName: String?
//可選圖示的名稱,在app的Info.plist檔案中宣告的CFBundleAlternateIcons中設定。
//如果要顯示應用程式的主圖示alternateIconName 傳nil即可,主圖示使用CFBundlePrimaryIcon宣告,CFBundleAlternateIcons與CFBundlePrimaryIcon兩個key都是CFBundleIcons的子條目

func setAlternateIconName(_ alternateIconName: String?, 
        completionHandler: ((Error?) -> Void)? = nil)
//更改應用程式的圖示
//completionHandler: 當有結果的時候的回撥
//成功改變圖示的的時候,error為nil,如果發生錯誤,error描述發生什麼了。並且alternateIconName的值保持不變
複製程式碼

具體的實現程式碼:

if #available(iOS 10.3, *) {
    //判斷是否支援替換圖示, false: 不支援
    guard UIApplication.shared.supportsAlternateIcons else { return }
    
    //如果支援, 替換icon
    UIApplication.shared.setAlternateIconName(imageStr) { (error) in
        if error != nil {
            print(error ?? "更換icon發生錯誤")
        } else {
            print("更換成功")
        }
    }
}

複製程式碼

三. 消除alert彈窗

  • 動態更換App圖示會有彈框, 有時候這個彈框看上去可能會很彆扭, 但是這個彈框是系統直接呼叫彈出的, 我們又如何消除呢
  • 通過層級關係可以看到這個彈框就是一個UIAlertController, 並且是通過presentViewController:animated:completion:方法彈出的
  • 所以可以考慮使用runtime, 攔截並替換該方法, 讓更換icon的時候, 不彈
  • 下面看一下具體程式碼:
extension NoAlertChangeViewController {
    fileprivate func runtimeReplaceAlert() {
        DispatchQueue.once(token: "UIAlertController") {
            let originalSelector = #selector(present(_:animated:completion:))
            let swizzledSelector = #selector(noAlert_present(_:animated:completion:))
            
            let originalMethod = class_getInstanceMethod(NoAlertChangeViewController.self, originalSelector)
            let swizzledMethod = class_getInstanceMethod(NoAlertChangeViewController.self, swizzledSelector)
            
            //交換實現的方法
            method_exchangeImplementations(originalMethod!, swizzledMethod!)
        }
    }
    
    @objc fileprivate func noAlert_present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Swift.Void)? = nil) {
        //判斷是否是alert彈窗
        if viewControllerToPresent.isKind(of: UIAlertController.self) {
            print("title: \(String(describing: (viewControllerToPresent as? UIAlertController)?.title))")
            print("message: \(String(describing: (viewControllerToPresent as? UIAlertController)?.message))")
            
            // 換圖示時的提示框的title和message都是nil,由此可特殊處理
            let alertController = viewControllerToPresent as? UIAlertController
            if alertController?.title == nil && alertController?.message == nil {
                //是更換icon的提示
                return
            } else {
                //其他的彈框提示正常處理
                noAlert_present(viewControllerToPresent, animated: flag, completion: completion)
            }
        }
        noAlert_present(viewControllerToPresent, animated: flag, completion: completion)
    }
}

複製程式碼
  • 這裡用到了DispatchQueue.once, 這個once是我對DispatchQueue加了一個擴充套件
  • 在Swift4.0以後, static dispatch_once_t onceToken;這個已經不能用了
  • 關於這方面的詳細介紹, 大家可以看看我的這篇文章--升級Swift4.0遇到的坑

四. 支援不同尺寸的Icon

  • 一個標準的Icon圖集, 需要十幾種尺寸, 比如: 20, 29, 40, 60等
  • 對於 info.plist 中的每個 icon 配置,CFBundleIconFiles 的值是一個陣列,我們可以在其中填入這十幾種規格的圖片名稱。經測試:
    • 檔案的命名沒有強制的規則,可以隨意取,
    • 陣列中的檔名也不關心先後順序。
  • 總之把對應的檔名填進去即可,它會自動選擇合適解析度的檔案(比如在 setting 中顯示 icon 時,它會找到提供的陣列中解析度為 29pt 的那個檔案)。
  • 具體相關官方文件可參考, 官方介紹
  • 首先, 針對不同的尺寸, 我們要有不同的命名, 具體參考下圖

  • 副檔名,如@2x,@3x,要麼統一不寫,那麼系統會自動尋找合適的尺寸。
  • 要寫就需要把每張icon的副檔名寫上,和上圖的格式一樣
  • 程式碼中呼叫圖片名, 更不需要加上尺寸:
        if #available(iOS 10.3, *) {
            //判斷是否支援替換圖示, false: 不支援
            guard UIApplication.shared.supportsAlternateIcons else { return }
            
            //如果支援, 替換icon
            UIApplication.shared.setAlternateIconName("Sunday") { (error) in
                //點選彈框的確認按鈕後的回撥
                if error != nil {
                    print(error ?? "更換icon發生錯誤")
                } else {
                    print("更換成功")
                }
            }
        }

複製程式碼

相關文章