反馈请联系hertz@hertzwang.com,谢谢
前言:用
Safari
的访问一个网址,然后将网址添加到主屏幕,这时会在主屏幕上生成一个书签(带有Icon和标题),点击该书签后会访问指定的网址,在指定的网址中使用“scheme://”的方式打开应用,关于iOS 14上的使用参考底部的iOS 14 示例
使用远程Web
使用Safari
打开链接并添加到主屏幕从而得到桌面快捷方式,通过window.navigator.standalone
来区分状态,在Safari
中加载时显示引导信息,独立全屏运行(点击主屏幕的快捷图标)时使用scheme
来打开App,例如:scheme://modules?id=1
HTML示例代码:
1 | <!DOCTYPE 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
:独立进行状态
参考:
iOS示例代码
1 | NSString *scheme = [NSString stringWithFormat:@"scheme://modules?id=%@", xxx]; // 组装Scheme |
使用本地Web
不支持iOS 14,在iOS 14以下可以使用开启本地服务的方式来实现,其原理为本地开启HTTP Server服务后打开网页,然后重定向到data:text/html
,添加到主屏幕的其实就是 数据形式的HTML ,这种数据形式可以独立运行不依赖服务,从而实现完全本地打开应用。
本地HTTP Server
使用第三方库 CocoaHTTPServer
或 GCDWebServer
来开启本地HTTP Server
网页部分
使用两个网页模板 Index.html
和 Content.html
,先替换 Content.html
中的 %ShortcutIcon%
和 %ShortcutName%
,再替换 Index.html
中的 %Content%
Index.html
1 | <!DOCTYPE html> |
Content.html
1 | <!DOCTYPE html> |
iOS 部分
启动 HTTP Server,访问 http://localhost:port/export.html
iOS 14 示例
依赖三方库
GCDWebServer
新建
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>新建
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;
@end1
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使用
[HttpServerUtil openSafariWithIcon:nil name:@"xxx" scheme:@"xx://"];