今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

iOS Add Desktop Shortcut

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, which needs certain functions within the APP to be added to the desktop, allowing users to click on the desktop shortcut to directly jump to the corresponding interface of the APP (similar to the example image below), so some research was conducted.

In fact, many APPs have already implemented similar features, such as Alipay, Cloud Flash Payment, etc. Each independent function can be added to the desktop separately, so there are many methods available online. What I did was to organize and experiment.

image

Implementation#

First, the operation flow for adding to the desktop function is:

Client opens APP -> Enters the corresponding APP function module -> Clicks the button to add a shortcut to the desktop -> Redirects to the browser and loads the guide page, clicks share, selects add to home screen -> Clicks the just added shortcut on the home screen, jumps to the corresponding interface of the APP.

iOS Development Internal Function Generate Desktop Shortcut.png

According to the information I have gathered, there are roughly two ways to implement this feature:

  • Method 1: H5 provides a webpage, with different webpages for each function, 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 my view, whether the server returns the URL or the client uses HttpServer is essentially different implementations of the server, so it is not categorized separately.

Preparation#

Step 1 Client: iOS Open the existing Xcode project, select Target, and add the URL Scheme. This URL Scheme is defined by yourself. 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.

image image

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, in application: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://") {
                // This indicates it is the APP's URL Scheme, process it
                
                let targetVC = targetViewController(from: url.path)
                if targetVC != nil {
                    // Check if the current displayed interface is the target interface to jump to
                    if topController.classForCoder == targetVC?.classForCoder {
                        return true
                    }
                    
                    navController.pushViewController(targetVC!, animated: true)
                }
                else {
                    return true
                }
            }
        }
        return true
    }

    // Return the target interface 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 Shortcuts for a Function in the App
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 on the <img class="icon" src="https://dariel-1256714552.cos.ap-shanghai.myqcloud.com/XEbFrgamEdvSxVFOBeuZ.png"> in 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 1#

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 of the desktop shortcut, where the format can choose to use DataURI style. 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:

  1. Configure the client's URLScheme.

  2. H5 provides the prepared webpage. If there is no H5, you can use the content of the above shortcuts.html, replacing the fields \(title), \(feature_icon), and \(urlToRedirect.absoluteString) with your APP's values, where apple-touch-icon-precomposed needs to contain the String of the icon after DataURI.

  3. 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 it. 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.

    image
  4. At the place where you click to add the shortcut, simply use openURL.

    
        func addMethod1(_ sender: Any) {
            // Method 1, 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 2#

Refer to Adding Shortcuts for a Function in the App, where the article uses the method of returning DataURI data from a self-built server on the client side. The specific operations are as follows:

  1. Configure the client's URL Schemes.

  2. The client uses Pod to add Swifter for self-built server.

  3. H5 provides the prepared webpage, using the content of the above shortcuts.html, without modifying the fields to be replaced.

  4. When clicking to add the shortcut, the client reads the content of the html 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 another issue to be resolved, which is: when using Method 1, 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 it did not jump to the APP. However, using the DataURI loading method in Method 2 does not have this issue, as each click on the icon can directly jump.

However, comparing with Alipay's add to desktop feature, it is found that Alipay also uses Method 1, and the first time the shortcut added from the desktop opens automatically jumps to Alipay, while 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:

image

How to achieve the effect of an intermediate page? Currently, my implementation of Method 1 relies on the H5 webpage content returned by the server. The script inside will directly self-redirect 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 the desktop - intermediate page - APP's URL Scheme; the second click is directly to show the intermediate page, and then manually click the button on the intermediate page to enter the APP again.

Let's try this:
First, edit the intermediate page 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 Immediately</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, then place this URL into the href of the previous webpage for self-redirect, and then use mocky to generate a link for the previous webpage. In the APP, use openURL to open the final generated link, run and debug.

The result is as expected, that is, the first open directly jumps, and the second open displays the intermediate page with a click-to-jump button; however, the style of the intermediate page looks different from Alipay's. The generated intermediate page shows the Safari secondary page style because it has gone through a jump once. This is not the effect I want, and after experiencing Alipay's effect, it is found that Alipay's intermediate page does not have the header and footer of the secondary page. So how is that achieved?

image

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 on one H5 page. Therefore, what I want now is to display the "Guide to Add to Desktop" style when jumping from the APP, and display the "Intermediate Page" style when opened from the desktop.

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 div, jump_container is the guide page div, and then based on window.navigator.standalone, set which div to display.

Ps:

To detect whether a web application is currently running in full-screen mode, simply 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 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 Immediately</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 on the <img class="icon" src="https://dariel-1256714552.cos.ap-shanghai.myqcloud.com/XEbFrgamEdvSxVFOBeuZ.png"> in 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, and when opened again, the page display is not blank, but as follows:

image

Summary#

The complete code is on: Github.

I feel that both methods have their pros and cons: Method 1 relies on the network because it requires the server to return the webpage content, and the next jump can only occur after loading is complete. Method 2 uses the DataURI method, where the data has already been converted to a string and placed locally, so it does not rely on the network. However, Method 1 is simpler to implement; although there is some redundancy in the cooperation between the client, H5, and the server, the workload is small and easy to achieve. Method 2's loading using DataURI makes it inconvenient to view and debug data. Based on my observations, Alipay actually uses Method 2, which can also load and open the main APP without a network, and on the basis of Method 2, it has added an intermediate page.

Attached images:

image

References#

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.