title: Adding Desktop Shortcuts for iOS APP
tags:
- Technology
- iOS
date: 2021-05-06 13:29#
Adding Desktop Shortcuts for iOS APP#
Background#
A new requirement has come in, needing certain functions within the APP to be added to the desktop, allowing users to click the desktop shortcut to directly jump to the corresponding APP interface (similar to the example image below), so some research was conducted.
In fact, many APPs have already implemented similar functions, such as Alipay, Cloud Flash Payment, etc. Each independent function can be added to the desktop separately, so there are many methods available online. The author has organized and experimented with these methods.

Implementation#
First, the operation flow for the add to desktop function is:
Client opens APP -> Enters the corresponding APP function module -> Clicks the add shortcut to desktop button -> Redirects to the browser and loads the guide page, clicks share, selects add to home screen -> Clicks the just added shortcut function from the home screen, jumps to the corresponding APP interface.
According to the information I have gathered, there are roughly two ways to implement this function:
-
Method 1: H5 provides a webpage, each different function provides a different webpage, the server returns the URLs of these webpages, the client configures to open the URL Scheme, and then uses Safari to directly load the URL. Depending on the entry method, the loaded webpage automatically redirects to open the APP's URL Scheme.
-
Method 2: H5 provides a generic webpage, the client replaces the content in the generic webpage, such as title, icon, etc., and converts it to DataURI format. The server provides an interface URL, the client configures to open the URL Scheme, uses Safari to load, and the interface returns a forced redirect to load the DataURI data.
Method 2 also has another implementation, where the client uses HttpServer to simulate the server. However, in the author's view, whether the server returns the URL or the client uses HttpServer, it is actually different implementations of the server, so it is not categorized separately.
Preparation#
Step 1 Client: Open an existing Xcode project on iOS, select the Target, and add a URL Scheme. This URL Scheme is user-defined. After defining xxx here, you can invoke the APP by entering xxx:// in the browser. For example, I defined mkaddtohomescreen, and then entering mkaddtohomescreen:// in the browser will prompt whether to open the corresponding APP.


After defining the Scheme, you can consider the issue of adding parameters to the Scheme. By adding parameters after the scheme, you can intercept them in the Appdelegate's application:open:options:
method and jump to different interfaces based on the corresponding parameters.
For example, if the Scheme is
mkaddtohomescreen://page/view1
, inapplication:open:options:
,url.absoluteString = mkaddtohomescreen://page/view1, url.host = page, url.path = /view1,
so you can jump to different interfaces based on the different paths.
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://") {
// Indicates it is the APP's URL Scheme, process
let targetVC = targetViewController(from: url.path)
if targetVC != nil {
// Check if the currently displayed interface is the one to jump to
if topController.classForCoder == targetVC?.classForCoder {
return true
}
navController.pushViewController(targetVC!, animated: true)
}
else {
return true
}
}
}
return true
}
// Return the interface to jump to based on the URL path
func targetViewController(from path: String) -> UIViewController? {
var targetVC: UIViewController?
switch path {
// Jump to different paths based on the URL's path
case "/view1":
targetVC = Method1ViewController()
break
case "/view2":
targetVC = Method2ViewController()
break
case "/view3":
targetVC = Method3ViewController()
break
default:
targetVC = nil
break
}
return targetVC
}
Step 2 H5 reference 47. Adding a Shortcut for a Function in the App in shortcuts.html, which consists of three parts:
- The header part defines the title of the webpage, as well as the icon and title displayed on the desktop shortcut.
- The body part defines the content of this webpage, which actually guides users on how to add it to the desktop.
- The script part performs a check to determine if the entry is from the desktop shortcut, and calls redirect accordingly.
The code is as follows:
<!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 code see link https://github.com/mokong/WKAddToHomeScreen */
</style>
<body>
<a id="redirect" href="\(urlToRedirect.absoluteString)"></a>
<div id="container">
<div class="main">
<div class="subject">Add Shortcut Function to Desktop</div>
</div>
<div class="guide">
<div class="content">
<p class="tips">
Click the <img class="icon" src="https://dariel-1256714552.cos.ap-shanghai.myqcloud.com/XEbFrgamEdvSxVFOBeuZ.png"> on the toolbar below
</p>
<p class="tips">
And select <img class="icon" src="https://dariel-1256714552.cos.ap-shanghai.myqcloud.com/IkKEhyTLQpYtqXMZBYtQ.png"> “<strong>Add to Home Screen</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>
For explanations regarding the desktop shortcut icon and title settings, refer to Apple's official Configuring Web Applications, as follows:
<!-- Specifying a Webpage Icon for Web Clip -->
<link rel="apple-touch-icon" href="touch-icon-iphone.png">
<!-- The difference between apple-touch-icon-precomposed and apple-touch-icon is that the former is the original image, while the latter is processed by Apple. You can choose to use either one -->
<!-- <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>
Method One#
H5 provides a webpage, such as the example code above, and then sets the content of the header part to be fixed:
- "apple-mobile-web-app-title" is the title of the desktop shortcut.
- "apple-touch-icon-precomposed" is the image for the desktop shortcut, where the format can choose to use DataURI style, and the generation method can refer to the following.
- "title" is the title of the page.
- \(urlToRedirect.absoluteString) is the defined URL Scheme link.
The Swift code to get the image DataURI format data is as follows:
// Get image DataURI format data
if let iconData = UIImage(named: "homeScreen")?.jpegData(compressionQuality: 0.5) {
let iconDataURI = iconData.base64EncodedString()
}
The specific steps are as follows:
-
Configure the client's URLScheme.
-
H5 provides the written webpage. If there is no H5, you can use the above shortcuts.html content, replacing the fields \(title), \(feature_icon), and \(urlToRedirect.absoluteString) with your APP's values. The apple-touch-icon-precomposed needs to contain the icon converted to DataURI String.
-
The server needs to provide a URL that returns this webpage, and then the client opens this URL. If the server is also unavailable... then just like me, use a simulated interface to return. Open mocky, (registration may be required), set Response Content Type to text/html, place the webpage content in the HTTP Response Body, and click the bottom GENERATE MY HTTP RESPONSE to generate a URL.
-
At the place where you click to add the shortcut, simply use openURL.
func addMethod1(_ sender: Any) { // Method one, does not require local H5 data, just needs to open the specified URL // You can use mocky to provide a simulated interface let urlStr = "https://run.mocky.io/v3/98baaf4a-edec-4956-8506-7bbfca349d07" UIApplication.shared.open(URL(string: urlStr)!, options: [:], completionHandler: nil) }
Method Two#
Refer to Adding a Shortcut for a Function in the App, the article uses the method of having the client self-build a server to return DataURI data, with specific operations as follows:
-
Configure the client's URL Schemes.
-
The client uses Pod to add Swifter for self-building the server.
-
H5 provides the written webpage, using the above shortcuts.html content, without changing the fields to be replaced.
-
When clicking to add the shortcut, the client reads the HTML content and replaces the specified fields, converts it to DataURI, starts the local server, and returns the DataURI data.
func addMethod2(_ sender: Any) { // Defined URL Scheme let schemeStr = "mkaddtohomescreen://page/view2" // The desktop shortcut icon to be replaced let shortcutImageData = UIImage(named: "homescreen")?.jpegData(compressionQuality: 0.5) // The desktop shortcut title to be replaced let shortcutTitle = "Add to Home Screen 2" guard let schemeURL = URL(string: schemeStr), let shortcutImageStr = shortcutImageData?.base64EncodedString() else { return } // Replace the content in H5 let htmlStr = htmlFor(title: shortcutTitle, urlToRedirect: schemeURL.absoluteString, icon: shortcutImageStr) guard let base64 = htmlStr.data(using: .utf8)?.base64EncodedString() else { return } // Start the local server, port number is 9081 if let shortcutUrl = URL(string: "http://localhost:9081/s") { // Convert to dataURI format 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 }
Not Done Yet#
The code has been placed on Github, and after downloading and running, you will find that there is still one issue to resolve, which is: when using method one, after adding the shortcut tag to the desktop, the first click on the desktop shortcut opens the APP; the second click on the desktop shortcut shows a white screen. The reason is that the first time the shortcut tag is opened, it was not closed, and when reopened, it did not trigger loading, so there was no jump to the APP. However, using the DataURI loading method in method two does not have this issue; each click on the icon can directly jump.
However, comparing with Alipay's add to desktop, it is found that Alipay also uses method one. The first time the shortcut added from the desktop opens automatically jumps to Alipay, and the second time clicking the desktop shortcut icon also stays on a page, but Alipay has placed something on this page, which can be called an intermediate page. As follows:

How to achieve the effect of an intermediate page? Currently, the author's implementation of method one relies on the server returning H5 webpage content, where the script will directly jump to open the APP's URL Scheme based on the different entry methods; therefore, to add an intermediate page, the idea is: to nest a layer. That is:
- The H5 webpage content returned by the server does not directly jump to open the APP's URL Scheme but instead jumps to the link of the intermediate page.
- The intermediate page, with the same logic, again jumps to open the APP's URL Scheme; at the same time, the intermediate page adds a button that also jumps to the APP's URL Scheme when clicked.
Thus, the first click is from desktop - intermediate page - APP's URL Scheme; the second click is directly showing the intermediate page, and then manually clicking on the intermediate page to enter the APP again.
Let's try this:
First, edit the intermediate page's H5, roughly the content is as follows, just for reference... just simply modify the body part of the previous H5 page, adding a button, the event is to click to open the Scheme, while the automatic jump logic to the Scheme also still exists.
<!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="Method Three Intermediate Page Content">
<title>"Method Three Intermediate Page Title"</title>
</head>
<script>document.documentElement.style.fontSize = 100 * document.documentElement.clientWidth / 375 + "px"</script>
<style>
/* css code see link 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">You are about to enter</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; width=100%; height=64px">Enter Now</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>
Place the intermediate page's webpage into mocky in the same way (set Response Content Type to text/html, place the webpage content in the HTTP Response Body), generate a URL, and then place this URL into the previous webpage's self-jump href, then use mocky to generate a link for the previous webpage, and in the APP, use openURL to open the last generated link, run and debug.
The result is as expected, that is, the first open directly jumps, and the second open shows the intermediate page with a click-to-jump button; however, the style of the intermediate page looks different from Alipay's. This generated intermediate page, because it went through a jump, displays the Safari secondary page style at the top and bottom. This is not the effect the author hopes for, and after experiencing Alipay's effect, it is found that Alipay's intermediate page does not have the style of a secondary page. So how is that achieved?

If you do not want the intermediate page to display as a secondary page, you cannot use the above method that goes through a jump. You can only use a single-page method, trying to do it all on one H5 page. So now what is wanted is, when jumping from the APP, to display the style of "Guide to Add to Desktop", and when opened from the desktop, to display the style of "Intermediate Page".
Following this logic, using two divs, including two styles, and setting the display of the two divs based on the different entry methods should work, right? So let's do it, putting the content and styles of the second HTML into the first HTML, the code is as follows: middle_container is the intermediate page's div, jump_container is the guide page's div, and then based on window.navigator.standalone, set which div to display or hide.
Ps:
To check whether the web application is currently running in full-screen mode, just check if window.navigator.standalone is true. If this property is true, it indicates that the web application is currently running in full-screen mode; otherwise, it is running in non-full-screen mode. This can be used to prompt users to add the web application icon to the home screen when it is detected that the web application is running in non-full-screen mode.
<!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="Method Three Title">
<link rel="apple-touch-icon-precomposed" href="data:image/jpeg;base64,imageData"/>
<title>Method Three Webpage Title</title>
</head>
<script>document.documentElement.style.fontSize = 100 * document.documentElement.clientWidth / 375 + "px"</script>
<style>
/* css code see link 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">You are about to enter</div>
<img class="middle_icon" src=""></img>
<div class="middle_appname">MKAddToHomeScreen</div>
<button class="middle_enter" onclick="jumpSchema()" style="background-color: #red; width=100%; height=64px">Enter Now</button>
</div>
<div id="jump_container">
<div class="main">
<div class="subject">Add Shortcut Function to Desktop</div>
</div>
<div class="guide">
<div class="content">
<p class="tips">
Click the <img class="icon" src="https://dariel-1256714552.cos.ap-shanghai.myqcloud.com/XEbFrgamEdvSxVFOBeuZ.png"> on the toolbar below
</p>
<p class="tips">
And select <img class="icon" src="https://dariel-1256714552.cos.ap-shanghai.myqcloud.com/IkKEhyTLQpYtqXMZBYtQ.png"> “<strong>Add to Home Screen</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>
Then place the current content into mocky to generate a link, open this link in the program, experience it, and Bingo. There is no secondary page style anymore, and when reopened, the page display is not blank, but as follows:

Summary#
The complete code is at: Github.
The author feels that both methods have their pros and cons: Method one relies on the network because it requires the server to return the webpage content, and the next jump can only occur after the loading is complete. Method two uses DataURI, which has the data already converted to a string stored locally, so it does not rely on the network. However, method one is simpler to implement; although it has some redundancy in client, H5, and server cooperation, the workload is small and easy to achieve. Method two's loading using DataURI makes it inconvenient to view and debug data. According to the author's observation, Alipay actually uses method two, as it can load and open the main APP without a network, and on the basis of method two, it also adds an intermediate page.
Attached images:
