Skip to content

Commit 4a973f4

Browse files
jcesarmobileimhoffd
authored andcommitted
feat(ios): Add URLSchemeHandler for iOS 11+ (#221)
* Add URLSchemeHandler for iOS 11+ * Make Scheme usage configurable
1 parent ade4f78 commit 4a973f4

File tree

6 files changed

+161
-13
lines changed

6 files changed

+161
-13
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ Thumbs.db
1414

1515
node_modules
1616
xcuserdata
17+
package-lock.json
1718

README.md

+20
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,26 @@ The default port the server will listen on. _You should change this to a random
5858

5959
Preferences only available for iOS platform
6060

61+
#### UseScheme
62+
63+
`<preference name="UseScheme" value="true" />`
64+
65+
Default value is `false`.
66+
67+
On iOS 11 and newer it will use a `WKURLSchemeHandler` that loads the app from `ionic://` scheme instead of using the local web server and `https://` scheme.
68+
69+
On iOS 10 and older will continue using the local web server even if the preference is set to `true`.
70+
71+
#### HostName
72+
73+
`<preference name="HostName" value="myHostName" />`
74+
75+
Default value is `app`.
76+
77+
If `UseScheme` is set to yes, it will use the `HostName` value as the host of the starting url.
78+
79+
Example `ionic://app`
80+
6181
#### WKSuspendInBackground
6282

6383
```xml

plugin.xml

+3
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
<config-file target="config.xml" parent="/*">
6363
<allow-navigation href="http://localhost:8080/*"/>
6464
<allow-navigation href="http://127.0.0.1:8080/*"/>
65+
<allow-navigation href="ionic://*" />
6566
<feature name="IonicWebView">
6667
<param name="ios-package" value="CDVWKWebViewEngine"/>
6768
</feature>
@@ -76,6 +77,8 @@
7677
<source-file src="src/ios/CDVWKWebViewUIDelegate.m"/>
7778
<header-file src="src/ios/CDVWKProcessPoolFactory.h"/>
7879
<source-file src="src/ios/CDVWKProcessPoolFactory.m"/>
80+
<header-file src="src/ios/IONAssetHandler.h"/>
81+
<source-file src="src/ios/IONAssetHandler.m"/>
7982
<asset src="src/ios/wk-plugin.js" target="wk-plugin.js"/>
8083

8184
<!--GCDWebServer headers-->

src/ios/CDVWKWebViewEngine.m

+53-13
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Licensed to the Apache Software Foundation (ASF) under one
2828
#import "CDVWKProcessPoolFactory.h"
2929
#import "GCDWebServer.h"
3030
#import "GCDWebServerPrivate.h"
31+
#import "IONAssetHandler.h"
3132

3233
#define CDV_BRIDGE_NAME @"cordova"
3334
#define CDV_IONIC_STOP_SCROLL @"stopScroll"
@@ -107,6 +108,8 @@ @interface CDVWKWebViewEngine ()
107108
@property (nonatomic, readwrite) CGRect frame;
108109
@property (nonatomic, strong) NSString *userAgentCreds;
109110
@property (nonatomic, assign) BOOL internalConnectionsOnly;
111+
@property (nonatomic, assign) BOOL useScheme;
112+
@property (nonatomic, strong) IONAssetHandler * handler;
110113

111114
@property (nonatomic, readwrite) NSString *CDV_LOCAL_SERVER;
112115
@end
@@ -152,6 +155,13 @@ - (void)initWebServer
152155
[GCDWebServer setLogLevel: kGCDWebServerLoggingLevel_Warning];
153156
self.webServer = [[GCDWebServer alloc] init];
154157

158+
[self updateBindPath];
159+
[self setServerPath:[self getStartPath]];
160+
161+
[self startServer];
162+
}
163+
164+
-(NSString *) getStartPath {
155165
NSString * wwwPath = [[NSBundle mainBundle] pathForResource:@"www" ofType: nil];
156166

157167
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
@@ -162,11 +172,8 @@ - (void)initWebServer
162172
NSString * snapshots = [cordovaDataDirectory stringByAppendingPathComponent:@"ionic_built_snapshots"];
163173
wwwPath = [snapshots stringByAppendingPathComponent:[persistedPath lastPathComponent]];
164174
}
165-
166-
[self updateBindPath];
167-
[self setServerPath:wwwPath];
168-
169-
[self startServer];
175+
self.basePath = wwwPath;
176+
return wwwPath;
170177
}
171178

172179
-(BOOL) isNewBinary
@@ -264,9 +271,22 @@ - (void)pluginInitialize
264271
{
265272
// viewController would be available now. we attempt to set all possible delegates to it, by default
266273
NSDictionary* settings = self.commandDelegate.settings;
267-
self.internalConnectionsOnly = [settings cordovaBoolSettingForKey:@"WKInternalConnectionsOnly" defaultValue:YES];
274+
if (@available(iOS 11.0, *)) {
275+
self.useScheme = [settings cordovaBoolSettingForKey:@"UseScheme" defaultValue:NO];
276+
} else {
277+
self.useScheme = NO;
278+
}
268279

269-
[self initWebServer];
280+
self.internalConnectionsOnly = [settings cordovaBoolSettingForKey:@"WKInternalConnectionsOnly" defaultValue:YES];
281+
if (self.useScheme) {
282+
NSString *bind = [settings cordovaSettingForKey:@"HostName"];
283+
if(bind == nil){
284+
bind = @"app";
285+
}
286+
self.CDV_LOCAL_SERVER = [NSString stringWithFormat:@"ionic://%@", bind];
287+
} else {
288+
[self initWebServer];
289+
}
270290

271291
self.uiDelegate = [[CDVWKWebViewUIDelegate alloc] initWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]];
272292

@@ -307,6 +327,15 @@ - (void)pluginInitialize
307327
WKWebViewConfiguration* configuration = [self createConfigurationFromSettings:settings];
308328
configuration.userContentController = userContentController;
309329

330+
if (@available(iOS 11.0, *)) {
331+
if (self.useScheme) {
332+
self.handler = [[IONAssetHandler alloc] init];
333+
[self.handler setAssetPath:[self getStartPath]];
334+
[configuration setURLSchemeHandler:self.handler forURLScheme:@"ionic"];
335+
[configuration setURLSchemeHandler:self.handler forURLScheme:@"ionic-asset"];
336+
}
337+
}
338+
310339
// re-create WKWebView, since we need to update configuration
311340
// remove from keyWindow before recreating
312341
[self.engineWebView removeFromSuperview];
@@ -459,7 +488,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
459488
if (context == KVOContext) {
460489
if (object == [self webView] && [keyPath isEqualToString: @"URL"] && [object valueForKeyPath:keyPath] == nil){
461490
NSLog(@"URL is nil. Reloading WKWebView");
462-
if ([self.webServer isRunning]) {
491+
if ([self isSafeToReload]) {
463492
[(WKWebView*)_engineWebView reload];
464493
} else {
465494
[self loadErrorPage:nil];
@@ -472,7 +501,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
472501

473502
- (void)onAppWillEnterForeground:(NSNotification *)notification {
474503
if ([self shouldReloadWebView]) {
475-
if ([self.webServer isRunning]) {
504+
if ([self isSafeToReload]) {
476505
NSLog(@"%@", @"CDVWKWebViewEngine reloading!");
477506
[(WKWebView*)_engineWebView reload];
478507
} else {
@@ -516,6 +545,11 @@ - (BOOL)shouldReloadWebView
516545
return [self shouldReloadWebView:wkWebView.URL title:wkWebView.title];
517546
}
518547

548+
- (BOOL)isSafeToReload
549+
{
550+
return [self.webServer isRunning] || self.useScheme;
551+
}
552+
519553
- (BOOL)shouldReloadWebView:(NSURL *)location title:(NSString*)title
520554
{
521555
BOOL title_is_nil = (title == nil);
@@ -551,7 +585,7 @@ - (id)loadRequest:(NSURLRequest *)request
551585
}
552586
request = [NSURLRequest requestWithURL:url];
553587
}
554-
if ([self.webServer isRunning]) {
588+
if ([self isSafeToReload]) {
555589
return [(WKWebView*)_engineWebView loadRequest:request];
556590
} else {
557591
return [self loadErrorPage:request];
@@ -831,7 +865,7 @@ - (void)webView:(WKWebView*)theWebView didFailNavigation:(WKNavigation*)navigati
831865

832866
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
833867
{
834-
if ([self.webServer isRunning]) {
868+
if ([self isSafeToReload]) {
835869
[webView reload];
836870
} else {
837871
[self loadErrorPage:nil];
@@ -912,9 +946,15 @@ -(void)getServerBasePath:(CDVInvokedUrlCommand*)command
912946
-(void)setServerBasePath:(CDVInvokedUrlCommand*)command
913947
{
914948
NSString * path = [command argumentAtIndex:0];
915-
[self setServerPath:path];
949+
if (self.useScheme) {
950+
self.basePath = path;
951+
[self.handler setAssetPath:path];
952+
} else {
953+
[self setServerPath:path];
954+
}
955+
916956
NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.CDV_LOCAL_SERVER]];
917-
if ([self.webServer isRunning]) {
957+
if ([self isSafeToReload]) {
918958
[(WKWebView*)_engineWebView loadRequest:request];
919959
} else {
920960
[self loadErrorPage:request];

src/ios/IONAssetHandler.h

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#import <Foundation/Foundation.h>
2+
#import <WebKit/WebKit.h>
3+
4+
@interface IONAssetHandler : NSObject <WKURLSchemeHandler>
5+
6+
@property (nonatomic, strong) NSString * basePath;
7+
8+
-(void)setAssetPath:(NSString *)assetPath;
9+
10+
@end

src/ios/IONAssetHandler.m

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#import "IONAssetHandler.h"
2+
#import <MobileCoreServices/MobileCoreServices.h>
3+
4+
@implementation IONAssetHandler
5+
6+
-(void)setAssetPath:(NSString *)assetPath {
7+
self.basePath = assetPath;
8+
}
9+
10+
- (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask
11+
API_AVAILABLE(ios(11.0)){
12+
NSString * startPath = @"";
13+
NSURL * url = urlSchemeTask.request.URL;
14+
NSString * stringToLoad = url.path;
15+
NSString * scheme = url.scheme;
16+
if ([scheme isEqualToString:@"ionic"]) {
17+
startPath = self.basePath;
18+
if ([stringToLoad isEqualToString:@""] || !url.pathExtension) {
19+
startPath = [startPath stringByAppendingString:@"/index.html"];
20+
} else {
21+
startPath = [startPath stringByAppendingString:stringToLoad];
22+
}
23+
} else {
24+
if (![stringToLoad isEqualToString:@""]) {
25+
startPath = stringToLoad;
26+
}
27+
}
28+
29+
NSData * data = [[NSData alloc] initWithContentsOfFile:startPath];
30+
NSInteger statusCode = 200;
31+
if (!data) {
32+
statusCode = 404;
33+
}
34+
NSURL * localUrl = [NSURL URLWithString:url.absoluteString];
35+
NSString * mimeType = [self getMimeType:url.pathExtension];
36+
id response = nil;
37+
if (data && [self isMediaExtension:url.pathExtension]) {
38+
response = [[NSURLResponse alloc] initWithURL:localUrl MIMEType:mimeType expectedContentLength:data.length textEncodingName:nil];
39+
} else {
40+
NSDictionary * headers = @{ @"Content-Type" : mimeType, @"Cache-Control": @"no-cache"};
41+
response = [[NSHTTPURLResponse alloc] initWithURL:localUrl statusCode:statusCode HTTPVersion:nil headerFields:headers];
42+
}
43+
44+
[urlSchemeTask didReceiveResponse:response];
45+
[urlSchemeTask didReceiveData:data];
46+
[urlSchemeTask didFinish];
47+
48+
}
49+
50+
- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask API_AVAILABLE(ios(11.0)){
51+
NSLog(@"stop");
52+
}
53+
54+
-(NSString *) getMimeType:(NSString *)fileExtension {
55+
if (fileExtension && ![fileExtension isEqualToString:@""]) {
56+
NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
57+
NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
58+
return contentType ? contentType : @"application/octet-stream";
59+
} else {
60+
return @"text/html";
61+
}
62+
}
63+
64+
-(BOOL) isMediaExtension:(NSString *) pathExtension {
65+
NSArray * mediaExtensions = @[@"m4v", @"mov", @"mp4",
66+
@"aac", @"ac3", @"aiff", @"au", @"flac", @"m4a", @"mp3", @"wav"];
67+
if ([mediaExtensions containsObject:pathExtension]) {
68+
return YES;
69+
}
70+
return NO;
71+
}
72+
73+
74+
@end

0 commit comments

Comments
 (0)