title: iOS APP 添加桌面快捷方式
tags:
- 技術
- iOS
date: 2021-05-06 13:29#
iOS APP 添加桌面快捷方式#
背景#
新接到一個需求,需要 APP 內的某些功能,能夠把入口添加到桌面,點擊桌面到入口可以直接跳轉 APP 對應界面(類似於下面這張示例圖),於是就做了一番調研。
其實很多 APP 目前都已經實現了類似的功能,比如支付寶、雲閃付等等,其中的每一個獨立功能都可以單獨添加到桌面,所以網上有很多實現的方法,筆者做的是整理和試錯。

實現#
首先,添加到桌面功能的操作流程是:
客戶端打開 APP -> 進入到對應到 APP 功能模塊 -> 點擊添加快捷方式到桌面按鈕 -> 跳轉瀏覽器,並加載引導頁面,點擊分享,選擇添加到主屏幕 -> 從主屏幕點擊剛剛添加到快捷功能,跳轉到 APP 的對應界面。
根據筆者了解到的信息,目前實現這種功能,大致可以分為兩種實現方式:
-
方法一:H5 提供網頁,每個不同的功能提供不同的網頁,服務端返回這些網頁的 URL,客戶端配置打開 URL Scheme,然後使用 Safari 直接加載 URL,加載的網頁中根據進入方式的不同,自動重定向打開 APP 的 URL Scheme。
-
方法二:H5 提供通用的網頁,客戶端替換通用網頁中的內容,比如標題、圖標等,並轉為 DataURI 格式,服務端提供接口 URL,客戶端配置打開 URL Scheme,使用 Safari 加載,接口返回強制重定向加載 DataURI 數據
其中方法二還有另一種實現方式,即客戶端使用 HttpServer 模擬服務端。但筆者來看,無論是服務端返回 URL 還是客戶端使用 HttpServer,其實是服務端的不同實現方式,故而沒有單獨分類。
準備#
第一步 客戶端:iOS 打開已有 Xcode 項目,選中 Target,添加 URL Scheme,這個 URL Scheme 是自己定義的,在這個地方定義了 xxx 之後,可以通過在瀏覽器中輸入 xxx:// 來喚起 APP,比如筆者定義了一個 mkaddtohomescreen,然後在瀏覽器中輸入 mkaddtohomescreen://,就會彈出是否打開對應 APP 的提示


定義好了 Scheme 之後,可以考慮 Scheme 添加參數的問題,通過在 scheme 後添加參數,在 Appdelegate 中applicaiton:open:options:
方法攔截到,根據對應參數跳轉不同界面
比如 Scheme 為
mkaddtohomescreen://page/view1
,在applicaiton:open:options:
中,url.absouluteString = mkaddtohomescreen://page/view1,url.host = page,url.path = /view1,
所以可以根據 path 的不同跳轉不同的界面。
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if let navController = window?.rootViewController as? UINavigationController,
let topController = navController.topViewController {
// eg: mkaddtohomescreen://page/view1
// url.host = page
// url.path = /view1
if url.absoluteString.hasPrefix("mkaddtohomescreen://") {
// 說明是APP的URL Scheme,處理
let targetVC = targetViewController(from: url.path)
if targetVC != nil {
// 判斷當前顯示的界面是否是要跳轉的界面
if topController.classForCoder == targetVC?.classForCoder {
return true
}
navController.pushViewController(targetVC!, animated: true)
}
else {
return true
}
}
}
return true
}
// 根據URL path返回要跳轉的界面
func targetViewController(from path: String) -> UIViewController? {
var targetVC: UIViewController?
switch path {
// 根據URL的path跳轉不同路徑
case "/view1":
targetVC = Method1ViewController()
break
case "/view2":
targetVC = Method2ViewController()
break
case "/view3":
targetVC = Method3ViewController()
break
default:
targetVC = nil
break
}
return targetVC
}
第二步 H5 參考47. 給 App 的某個功能添加快捷方式
中的 shortcuts.html,其中共有三部分,大致為:
- header 部分定義了網頁的標題,以及顯示到桌面快捷方式的圖標和標題
- body 部分則定義來這個網頁的內容,其實是引導用戶如何添加到桌面
- script 部分則是做了一個判斷,判斷是桌面快捷方式進入的情況,自己調用 redirect
代碼如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="#ffffff">
<meta name="apple-mobile-web-app-title" content="\(title)">
<link rel="apple-touch-icon-precomposed" href="data:image/jpeg;base64,\(feature_icon)"/>
<title>\(title)</title>
</head>
<script>document.documentElement.style.fontSize = 100 * document.documentElement.clientWidth / 375 + "px"</script>
<style>
/* css代碼見鏈接 https://github.com/mokong/WKAddToHomeScreen */
</style>
<body>
<a id="redirect" href="\(urlToRedirect.absoluteString)"></a>
<div id="container">
<div class="main">
<div class="subject">添加快捷功能到桌面</div>
</div>
<div class="guide">
<div class="content">
<p class="tips">
點擊下方工具欄上的<img class="icon" src="https://dariel-1256714552.cos.ap-shanghai.myqcloud.com/XEbFrgamEdvSxVFOBeuZ.png">
</p>
<p class="tips">
並選擇<img class="icon" src="https://dariel-1256714552.cos.ap-shanghai.myqcloud.com/IkKEhyTLQpYtqXMZBYtQ.png">“<strong>添加到主屏幕</strong>”
</p>
<img class="toolbar" src="https://dariel-1256714552.cos.ap-shanghai.myqcloud.com/oFNuXVhPJYvBDJPXJTmt.jpg">
<img class="arrow" src="https://dariel-1256714552.cos.ap-shanghai.myqcloud.com/FlBEnTRnlhMyLyVhlfZT.png">
</div>
</div>
</div>
</body>
</html>
<script type="text/javascript">
if (window.navigator.standalone) {
var element = document.getElementById('container');
element.style.display = "none";
var element = document.getElementById('redirect');
var event = document.createEvent('MouseEvents');
event.initEvent('click', true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
document.body.style.backgroundColor = '#FFFFFF';
setTimeout(function() { element.dispatchEvent(event); }, 25);
} else {
var element = document.getElementById('container');
element.style.display = "inline";
}
</script>
其中有關於涉及到桌面快捷方式圖標和標題設置的解釋可參考蘋果官方的Configuring Web Applications,如下
<!-- Specifying a Webpage Icon for Web Clip -->
<link rel="apple-touch-icon" href="touch-icon-iphone.png">
<!-- apple-touch-icon-precomposed與apple-touch-icon的區別為前者是原圖,後者是會被蘋果處理的圖片,這兩個的使用二選一即可
<link rel="apple-touch-icon-precomposed" href="xxx.png"> -->
<!-- Specifying a Launch Screen Image -->
<link rel="apple-touch-startup-image" href="/launch.png">
<!-- Adding a Launch Icon Title -->
<meta name="apple-mobile-web-app-title" content="AppTitle">
<!-- Hiding Safari User Interface Components -->
<meta name="apple-mobile-web-app-capable" content="yes">
<!-- Changing the Status Bar Appearance> -->
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<!-- Linking to Other Native Apps -->
<a href="tel:1-408-555-5555">Call me</a>
方法一#
H5 提供網頁,比如上面示例的代碼,然後把其中 header 部分的內容設置為固定的
- "apple-mobile-web-app-title" 是桌面快捷方式的標題
- "apple-touch-icon-precomposed" 是桌面快捷方式的圖片,其中的格式可以選擇使用 DataURI 的這種樣式,生成方法可以參考下面的
- "title" 則是頁面的標題
- \(urlToRedirect.absoluteString) 是定義好的 URL Scheme 鏈接
獲取圖片 DataURI 格式的數據 Swift 的代碼如下
// 獲取圖片DataURI格式的數據
if let iconData = UIImage(named: "homeScreen")?.jpegData(compressionQuality: 0.5) {
let iconDataURI = iconData.base64EncodedString()
}
具體步驟如下:
-
配置好客戶端的 URLScheme
-
H5 提供編寫好的網頁,如果沒有 H5,可使用上面的 shortcuts.html 內容,把其中的待替換字段 \(title)、\(feature_icon)、以及 \(urlToRedirect.absoluteString) 設置為自己 APP 的,其中的 apple-touch-icon-precomposed 需要放圖標經過 DataURI 後的 String
-
需要服務端提供 URL,返回這個網頁,然後客戶端打開這個 URL。如果服務端也沒有。。。那就跟我一樣,使用模擬接口返回,打開mocky,(可能需要註冊),Response Content Type 設置為 text/html,HTTP Response Body 中放入下面的網頁內容,然後點擊底部的 GENERATE MY HTTP RESPONSE,就會生成一個 URL
-
在點擊添加快捷方式的地方,直接 openURL 即可
func addMethod1(_ sender: Any) { // 方法一,不需要本地放H5數據,只需要打開指定URL即可 // 可使用mocky來提供模擬接口 let urlStr = "https://run.mocky.io/v3/98baaf4a-edec-4956-8506-7bbfca349d07" UIApplication.shared.open(URL(string: urlStr)!, options: [:], completionHandler: nil) }
方法二#
可參考給 App 的某個功能添加快捷方式,文章中使用的是客戶端自建服務器返回 DataURI 數據的方法,具體操作如下:
-
配置好客戶端的 URL Schemes
-
客戶端使用 Pod 添加 Swifter,用於自建服務器
-
H5 提供編寫好的網頁,使用上面的 shortcuts.html 內容,其中的待替換字段不要動
-
在點擊添加快捷方式時,客戶端讀取 html 的內容並替換裡面指定字段,轉為 DataURI,啟動本地服務器,並返回 DataURI 數據
func addMethod2(_ sender: Any) { // 定義好的URL Scheme let schemeStr = "mkaddtohomescreen://page/view2" // 要替換的桌面快捷方式圖標 let shortcutImageData = UIImage(named: "homescreen")?.jpegData(compressionQuality: 0.5) // 要替換的桌面快捷方式標題 let shortcutTitle = "添加到主屏幕2" guard let schemeURL = URL(string: schemeStr), let shortcutImageStr = shortcutImageData?.base64EncodedString() else { return } // 替換H5中的內容 let htmlStr = htmlFor(title: shortcutTitle, urlToRedirect: schemeURL.absoluteString, icon: shortcutImageStr) guard let base64 = htmlStr.data(using: .utf8)?.base64EncodedString() else { return } // 啟動本地服務器,端口號是9081 if let shortcutUrl = URL(string: "http://localhost:9081/s") { // 轉為dataURI格式 let server = HttpServer() server["/s"] = { request in return .movedPermanently("data:text/html;base64,\(base64)") } try? server.start(9081) UIApplication.shared.open(shortcutUrl, options: [:], completionHandler: nil) } } func htmlFor(title: String, urlToRedirect: String, icon: String) -> String { let shortcutsPath = Bundle.main.path(forResource: "content2", ofType: "html") var shortcutsContent = try! String(contentsOfFile: shortcutsPath!) as String shortcutsContent = shortcutsContent.replacingOccurrences(of: "\\(title)", with: title) shortcutsContent = shortcutsContent.replacingOccurrences(of: "\\(urlToRedirect.absoluteString)", with: urlToRedirect) shortcutsContent = shortcutsContent.replacingOccurrences(of: "\\(feature_icon)", with: icon) print(shortcutsContent) return shortcutsContent }
還沒完#
代碼放到了Github,大家下載運行後會發現還有一個問題待解決,即:當使用方法一,添加快捷標籤到桌面後,第一次點擊桌面的快捷標籤打開了 APP;再次點擊桌面的快捷標籤,顯示白屏,原因是第一次打開快捷標籤沒有關閉,重新打開時沒有觸發加載,所以也就沒有跳轉 APP。而使用了 DataURI 加載的方法二,則沒有這個問題,每次點擊圖標均可以直接跳轉。
但是對比支付寶的添加到桌面發現支付寶的也是採用的方法一,第一次從桌面添加的快捷打開時自動跳轉到支付寶,第二次點擊桌面到快捷圖標時,發現也是停留在一個頁面,但是支付寶在這個頁面上放了東西,可以稱之為中間頁。如下

要怎麼實現中間頁那種效果呢,目前筆者方法一的實現,點擊時依賴的是服務端返回的 H5 網頁內容,裡面的 Script 會根據進入方式的不同,直接自跳轉打開 APP 的 URL Scheme;所以想要添加中間頁,嗯,想法是:嵌套一層。即:
- 服務端返回的 H5 網頁內容,裡面的 Script 不直接跳轉打開 APP 的 URL Scheme,而是跳轉中間頁的鏈接
- 中間頁的頁面,同樣的邏輯,再次跳轉打開 APP 的 URLScheme;同時中間頁的頁面添加按鈕,點擊也是跳轉 APP 的 URLScheme。
這樣,第一次點擊時,是桌面 - 中間頁 - APP 的 URL Scheme;第二次點擊時,則是直接顯示中間頁,然後手動點擊中間頁上的立即進入,再次打開 APP。
下面來嘗試一下:
首先編輯中間頁面的 H5,大致內容如下,僅供參考。。。。就是把之前 H5 頁面的 body 部分簡單修改一下,添加一個 button,事件是點擊打開 Scheme,同時自動跳轉 Scheme 的邏輯也還存在。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="#ffffff">
<meta name="apple-mobile-web-app-title" content="方法三中間頁內容">
<title>"方法三中間頁標題"</title>
</head>
<script>document.documentElement.style.fontSize = 100 * document.documentElement.clientWidth / 375 + "px"</script>
<style>
/* css代碼見鏈接 https://github.com/mokong/WKAddToHomeScreen */
</style>
<body>
<a id="redirect" href="mkaddtohomescreen://page/view3"></a>
<div id="B_container" class="backguide" style="display: block; width='100%'; background-color=#cyan">
<div class="tips">你即將進入</div>
<img id="B_icon" class="icon" src=""></img>
<div id="B_appname" class="appname">MKAddToHomeScreen</div>
<button class="enter" onclick="jumpSchema()" style="background-color: #red; widht=100%; height=64px">立即進入</button>
</div>
</body>
</html>
<script type="text/javascript">
function jumpSchema() {
var element = document.getElementById('redirect');
var event = document.createEvent('MouseEvents');
event.initEvent('click', true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
document.body.style.backgroundColor = '#FFFFFF';
setTimeout(function() { element.dispatchEvent(event); }, 25);
}
if (window.navigator.standalone) {
var element = document.getElementById('redirect');
var event = document.createEvent('MouseEvents');
event.initEvent('click', true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
document.body.style.backgroundColor = '#FFFFFF';
setTimeout(function() { element.dispatchEvent(event); }, 25);
} else {
var element = document.getElementById('container');
element.style.display = "inline";
}
</script>
把中間頁的網頁放到mocky中按照同樣的方式 (Response Content Type 設置為 text/html,HTTP Response Body 中放入網頁內容),生成一個 URL,然後把這個 URL 放到之前網頁要自跳轉的 href 中,然後再把之前網頁再用mocky生成一個鏈接,在 APP 中使用 openURL 的方式打開最後生成的這個鏈接,運行,調試。
發現結果是期望的,即第一次打開直接跳轉,第二次打開顯示中間頁上面有點擊跳轉按鈕;但是中間頁的樣式看起來確跟支付寶的不一樣,這樣生成的中間頁因為經過了一次跳轉,所以頂部和底部都顯示了 Safari 二級頁面的樣式,嗯哼,這個不是筆者所希望的效果,而且體驗支付寶的效果之後,發現支付寶的中間頁是沒有二級頁的那種頭部和底部的,所以,那是怎麼實現的呢?

如果不想要中間頁顯示為二級頁面的形式,就不能採用上面那種經過一次跳轉方法。只能採用單一頁面的方法,在一個 H5 頁面上想辦法。所以現在想要的是,在同一個頁面上,從 APP 跳轉的時候顯示 “引導添加到桌面” 的樣式,從桌面打開時顯示 “中間頁” 的樣式。
按照這個邏輯來,用兩個 div,包括兩段樣式,根據進入方式的不同,設置兩個 div 的顯示隱藏是不是就可以了呢?說做就做,把上面第二個 html 中的內容和樣式放到第一個 html 中,代碼如下:middle_container 是中間頁的 div,jump_container 是引導頁 div,然後根據 window.navigator.standalone 判斷顯示哪一個 div,middle_container 中按鈕點擊是跳轉打開 APP,同時再把第一個 html 的跳轉由跳轉中間頁改為打開 APP
Ps:
要檢測 Web 應用程序當前是否運行在全屏狀態,只要檢測 window.navigator.standalone 是否為 true 就可以了,如果這個屬性為 true 則表示 Web 應用程序當前運行在全屏狀態,否則運行在非全屏狀態。可用於檢測到 Web 應用程序運行在非全屏狀態時提示用戶把 Web 應用程序的圖標添加到主屏幕。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="#ffffff">
<meta name="apple-mobile-web-app-title" content="方法三標題">
<link rel="apple-touch-icon-precomposed" href="data:image/jpeg;base64,imageData"/>
<title>方法三網頁標題</title>
</head>
<script>document.documentElement.style.fontSize = 100 * document.documentElement.clientWidth / 375 + "px"</script>
<style>
/* css代碼見鏈接 https://github.com/mokong/WKAddToHomeScreen */
</style>
<body>
<a id="redirect" href="mkaddtohomescreen://page/view3"></a>
<div id="middle_container" class="backguide">
<div class="middle_tips">你即將進入</div>
<img class="middle_icon" src=""></img>
<div class="middle_appname">MKAddToHomeScreen</div>
<button class="middle_enter" onclick="jumpSchema()" style="background-color: #red; widht=100%; height=64px">立即進入</button>
</div>
<div id="jump_container">
<div class="main">
<div class="subject">添加快捷功能到桌面</div>
</div>
<div class="guide">
<div class="content">
<p class="tips">
點擊下方工具欄上的<img class="icon" src="https://dariel-1256714552.cos.ap-shanghai.myqcloud.com/XEbFrgamEdvSxVFOBeuZ.png">
</p>
<p class="tips">
並選擇<img class="icon" src="https://dariel-1256714552.cos.ap-shanghai.myqcloud.com/IkKEhyTLQpYtqXMZBYtQ.png">“<strong>添加到主屏幕</strong>”
</p>
<img class="toolbar" src="https://dariel-1256714552.cos.ap-shanghai.myqcloud.com/oFNuXVhPJYvBDJPXJTmt.jpg">
<img class="arrow" src="https://dariel-1256714552.cos.ap-shanghai.myqcloud.com/FlBEnTRnlhMyLyVhlfZT.png">
</div>
</div>
</div>
</body>
</html>
<script type="text/javascript">
function jumpSchema() {
var element = document.getElementById('redirect');
var event = document.createEvent('MouseEvents');
event.initEvent('click', true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
document.body.style.backgroundColor = '#FFFFFF';
setTimeout(function() { element.dispatchEvent(event); }, 25);
}
if (window.navigator.standalone) {
var middle_element = document.getElementById('middle_container');
var jump_element = document.getElementById('jump_container');
middle_element.style.display = "inline";
jump_element.style.display = "none"
var element = document.getElementById('redirect');
var event = document.createEvent('MouseEvents');
event.initEvent('click', true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
document.body.style.backgroundColor = '#FFFFFF';
setTimeout(function() { element.dispatchEvent(event); }, 25);
} else {
var middle_element = document.getElementById('middle_container');
var jump_element = document.getElementById('jump_container');
middle_element.style.display = "none";
jump_element.style.display = "inline";
}
</script>
然後用當前內容放到mocky生成一個鏈接,在程序中打開這個鏈接,體驗,Binggo。沒有了二級界面的樣式,而且再次打開,頁面顯示也不是空白,而是如下樣式:

總結#
完整代碼在:Github。
筆者感覺兩種方式各有優缺點:方法一依賴於網絡,因為需要服務端返回的網頁內容,加載完成後才能進行下一步跳轉。而方法二採用 DataURI 方式的,把數據已經轉為 string 放在了本地,點擊時直接加載,故而不依賴網絡。但方法一實現簡單,客戶端、H5、和服務端配合雖然有些冗餘,但工作量小,很容易實現。方法二的加載採用 DataURI,查看調試數據不方便。根據筆者的觀察,支付寶其實採用的是方法二,沒網絡的時候也可以加載打開主 APP,且在方法二的基礎上還加上了中間頁。
附圖:
