0%

iOS应用添加到主屏幕(支持iOS 14)

反馈请联系hertz@hertzwang.com,谢谢

前言:用Safari的访问一个网址,然后将网址添加到主屏幕,这时会在主屏幕上生成一个书签(带有Icon和标题),点击该书签后会访问指定的网址,在指定的网址中使用“scheme://”的方式打开应用,关于iOS 14上的使用参考底部的iOS 14 示例

使用远程Web

使用Safari打开链接并添加到主屏幕从而得到桌面快捷方式,通过window.navigator.standalone来区分状态,在Safari中加载时显示引导信息,独立全屏运行(点击主屏幕的快捷图标)时使用scheme来打开App,例如:scheme://modules?id=1

HTML示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<html>
<head>
<meta name="apple-mobile-web-app-capable" content="yes" >
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta content="text/html charset=UTF-8" http-equiv="Content-Type"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no"/>
<link rel='apple-touch-icon' href='https://xxx.png'>
<title>主屏幕显示的名称</title>
</head>
<body>
<a id="startApp" style="display:none"></a>
<div id="msg">
<div class="main">
<div>点击下方工具栏上\n并点击添加到主屏幕</div>
</div>
</div>
</body>
<script>
var url = location.search; // 获取url中"?"符后的字串
if (window.navigator.standalone == true) {
document.getElementById("msg").innerHTML= '';
document.getElementById("startApp").href = url.substring(1);
document.getElementById("startApp").click();
}
</script>
<style>
.main {
color: #333;
text-align: center;
width: auto;
}
</style>
</html>
  • apple-mobile-web-app-capable :仅支持Safari浏览器,以独立进程运行时全屏显示(Safari中输入网址打开链接称之为浏览器内打开,添加到主屏幕后,在主屏幕中点击生成的书签运行称之为独立进程运行)
  • apple-mobile-web-app-status-bar-style :仅支持Safari浏览器,设置状态栏颜色,apple-mobile-web-app-capable为YES时且独立进程运行时生效
  • apple-touch-icon:书签图标
  • title:标签内容为书签的名称
  • window.navigator.standalone:独立进行状态

参考:

Safari Supported Meta Tags

iOS示例代码

1
2
3
4
NSString *scheme = [NSString stringWithFormat:@"scheme://modules?id=%@", xxx]; // 组装Scheme
NSString *urlString = [NSString stringWithFormat:@"http://domainName:port/index.html?%@", scheme]; // 完整html路径
NSURL *url = [NSURL URLWithString:urlString];
[[UIApplication sharedApplication] openURL:url]; // 使用Safari打开网址

使用本地Web

不支持iOS 14,在iOS 14以下可以使用开启本地服务的方式来实现,其原理为本地开启HTTP Server服务后打开网页,然后重定向到data:text/html,添加到主屏幕的其实就是 数据形式的HTML ,这种数据形式可以独立运行不依赖服务,从而实现完全本地打开应用。

本地HTTP Server

使用第三方库 CocoaHTTPServerGCDWebServer 来开启本地HTTP Server

网页部分

使用两个网页模板 Index.htmlContent.html,先替换 Content.html 中的 %ShortcutIcon%%ShortcutName%,再替换 Index.html 中的 %Content%

Index.html

1
2
3
4
5
6
7
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="refresh" content="0;URL= data:text/html;charset=utf-8,%Content%">
</head>
</html>

Content.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<html>
<head>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta content="text/html charset=UTF-8" http-equiv="Content-Type" />
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no" />
<link rel='apple-touch-icon' href='%ShortcutIcon%'><!-- base64的图片 -->
<title>%ShortcutName%</title>
</head>
<body>
<a href="%AppScheme%" id="startApp" style="display:none"></a>
<div id="msg">
<div class="main">
<div>点击下方工具栏上\n并点击添加到主屏幕</div>
</div>
</div>
</body>
<script>
if (window.navigator.standalone == true) {
document.getElementById("msg").innerHTML= '';
var lnk = document.getElementById("startApp").click();
}
</script>
<style>
.main {
color: #333;
text-align: center;
width: auto;
}
</style>
</html>

iOS 部分

启动 HTTP Server,访问 http://localhost:port/export.html

iOS 14 示例

  1. 依赖三方库GCDWebServer

  2. 新建AddToHomeScreenContent.html,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <!DOCTYPE html>
    <html>
    <head>
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta content="text/html charset=UTF-8" http-equiv="Content-Type" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no" />
    <link rel='apple-touch-icon' href='%ShortcutIcon%'><!-- base64的图片 -->
    <title>%ShortcutName%</title>
    </head>
    <body>
    <a href="%AppScheme%" id="startApp" style="display:none"></a>
    <div id="msg">
    <div class="main">
    <div>点击下方工具栏上的</div>
    <div>并点击添加到主屏幕</div>
    </div>
    <img class="tips" src="%TipsImage%"/>
    </div>
    </body>
    <script>
    if (window.navigator.standalone == true) {
    document.getElementById("msg").innerHTML= '';
    var lnk = document.getElementById("startApp").click();
    }
    </script>
    <style>
    .main {
    color: #333;
    text-align: center;
    width: auto;
    }
    .tips {
    width: %TipsImageWidth%;
    position: absolute;
    left: %TipsImageLeft%;
    bottom: 10px;
    }
    </style>
    </html>

  3. 新建HttpServerUtil : NSObject

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @interface HttpServerUtil : NSObject

    + (instancetype)shared;

    /// 添加到主屏幕
    /// @param image 桌面图标,为nil时使用应用Icon
    /// @param name 桌面显示的名称
    /// @param scheme 当前应用scheme
    + (void)openSafariWithIcon:(UIImage * _Nullable)image name:(NSString *)name scheme:(NSString *)scheme;

    - (void)start;
    - (void)stop;

    @end

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    #import "HttpServerUtil.h"
    #import <GCDWebServer.h>
    #import <GCDWebServerDataResponse.h>

    @interface HttpServerUtil ()

    @property (nonatomic, strong) GCDWebServer *webServer;
    @property (nonatomic, strong) NSString *base64ImageString;
    @property (nonatomic, strong) NSString *appName;
    @property (nonatomic, strong) NSString *appScheme;
    @property (nonatomic, strong) UIImage *appIconImage;
    @property (nonatomic, strong) NSURL *httpServerURL;

    @end

    @implementation HttpServerUtil

    #pragma mark - Override

    - (instancetype)init
    {
    self = [super init];
    if (self) {
    __weak typeof(self) weak_self = self;
    [self.webServer addDefaultHandlerForMethod:@"GET"
    requestClass:[GCDWebServerRequest class]
    processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {
    if (weak_self) {
    __strong typeof(weak_self) self = weak_self;
    return [self _configResponse];
    } else {
    return nil;
    }
    }];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
    }
    return self;
    }

    #pragma mark - Public

    + (instancetype)shared {
    static HttpServerUtil *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    instance = [[HttpServerUtil alloc] init];
    });
    return instance;
    }

    + (void)openSafariWithIcon:(UIImage *)image name:(nonnull NSString *)name scheme:(nonnull NSString *)scheme {
    HttpServerUtil *serverUtil = [HttpServerUtil shared];
    serverUtil.base64ImageString = [serverUtil _base64WithImage:image icon:YES];
    serverUtil.appName = name;
    serverUtil.appScheme = scheme;
    [serverUtil start];
    [[UIApplication sharedApplication] openURL:serverUtil.httpServerURL options:@{} completionHandler:^(BOOL success) {

    }];
    }

    - (void)start {
    if ([self.webServer isRunning]) {
    return;
    }
    NSUInteger port = 12315;
    [self.webServer startWithPort:port bonjourName:nil];
    self.httpServerURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%lu", port]];
    }

    - (void)stop {
    if (![self.webServer isRunning]) {
    return;
    }
    [self.webServer stop];
    }

    #pragma mark - Private

    - (GCDWebServerResponse *)_configResponse {
    // 设置名称、Icon、Scheme
    NSString *contentHtmlPath = [[NSBundle mainBundle] pathForResource:@"AddToHomeScreenContent" ofType:@"html"];
    NSMutableString *contentHtml = [[NSMutableString alloc] initWithContentsOfFile:contentHtmlPath encoding:NSUTF8StringEncoding error:NULL];
    NSString *base64TipsImage = [self _base64WithImage:[UIImage imageNamed:@""] icon:NO];
    NSString *tipsImageWidth = [NSString stringWithFormat:@"%lfpx", [[UIScreen mainScreen] bounds].size.width - 15 * 2];
    NSString *tipsImageLeft = [NSString stringWithFormat:@"%lfpx", 15.0f];
    NSDictionary *variables = @{
    @"ShortcutIcon" : self.base64ImageString,
    @"ShortcutName" : self.appName,
    @"AppScheme" : self.appScheme,
    @"TipsImage" : base64TipsImage,
    @"TipsImageWidth" : tipsImageWidth,
    @"TipsImageLeft" : tipsImageLeft,
    };
    [variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) {
    [contentHtml replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, contentHtml.length)];
    }];
    NSString *urlStringEncode = [contentHtml stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
    NSString *html = [NSString stringWithFormat:@"data:text/html;charset=utf-8,%@", urlStringEncode];
    GCDWebServerResponse *rsp = [GCDWebServerResponse responseWithRedirect:self.httpServerURL permanent:YES];
    [rsp setValue:html forAdditionalHeader:@"Location"]; // 设置URLEncoded的html
    return rsp;
    }

    - (NSString *)_base64WithImage:(UIImage * _Nullable)image icon:(BOOL)isIcon {
    if (isIcon) {
    image = (image ? [image sd_resizedImageWithSize:CGSizeMake(180, 180) scaleMode:SDImageScaleModeAspectFill] : self.appIconImage);
    }
    NSData *data = UIImagePNGRepresentation(image);
    NSString *encodedImageStr = [data base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
    NSString *result = [NSString stringWithFormat:@"data:image/png;base64,%@", encodedImageStr];
    return result;
    }

    - (void)_applicationWillEnterForeground {
    [self stop];
    }

    #pragma mark - Getter

    - (GCDWebServer *)webServer {
    if (_webServer == nil) {
    _webServer = [[GCDWebServer alloc] init];
    }
    return _webServer;
    }

    - (UIImage *)appIconImage {
    if (_appIconImage == nil) {
    NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
    NSArray *iconsArr = infoDict[@"CFBundleIcons"][@"CFBundlePrimaryIcon"][@"CFBundleIconFiles"];
    NSString *iconLastName = [iconsArr lastObject];
    _appIconImage = [UIImage imageNamed:(iconLastName ?: @"AppIcon")];
    }
    return _appIconImage;
    }

    @end
  4. 使用 [HttpServerUtil openSafariWithIcon:nil name:@"xxx" scheme:@"xx://"];