- 在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("更換成功")
}
}
}
複製程式碼
- 具體可參考demo--github專案地址