diff --git a/.gitignore b/.gitignore
index 30b09b4d..8ef01939 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,4 +14,5 @@ Thumbs.db
node_modules
xcuserdata
+package-lock.json
diff --git a/README.md b/README.md
index 6e4bc269..19355761 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,26 @@ The default port the server will listen on. _You should change this to a random
Preferences only available for iOS platform
+#### UseScheme
+
+``
+
+Default value is `false`.
+
+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.
+
+On iOS 10 and older will continue using the local web server even if the preference is set to `true`.
+
+#### HostName
+
+``
+
+Default value is `app`.
+
+If `UseScheme` is set to yes, it will use the `HostName` value as the host of the starting url.
+
+Example `ionic://app`
+
#### WKSuspendInBackground
```xml
diff --git a/plugin.xml b/plugin.xml
index d606bd4c..4c6858c8 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -62,6 +62,7 @@
+
@@ -76,6 +77,8 @@
+
+
diff --git a/src/ios/CDVWKWebViewEngine.m b/src/ios/CDVWKWebViewEngine.m
index a0e27f78..e079f95c 100644
--- a/src/ios/CDVWKWebViewEngine.m
+++ b/src/ios/CDVWKWebViewEngine.m
@@ -28,6 +28,7 @@ Licensed to the Apache Software Foundation (ASF) under one
#import "CDVWKProcessPoolFactory.h"
#import "GCDWebServer.h"
#import "GCDWebServerPrivate.h"
+#import "IONAssetHandler.h"
#define CDV_BRIDGE_NAME @"cordova"
#define CDV_IONIC_STOP_SCROLL @"stopScroll"
@@ -107,6 +108,8 @@ @interface CDVWKWebViewEngine ()
@property (nonatomic, readwrite) CGRect frame;
@property (nonatomic, strong) NSString *userAgentCreds;
@property (nonatomic, assign) BOOL internalConnectionsOnly;
+@property (nonatomic, assign) BOOL useScheme;
+@property (nonatomic, strong) IONAssetHandler * handler;
@property (nonatomic, readwrite) NSString *CDV_LOCAL_SERVER;
@end
@@ -152,6 +155,13 @@ - (void)initWebServer
[GCDWebServer setLogLevel: kGCDWebServerLoggingLevel_Warning];
self.webServer = [[GCDWebServer alloc] init];
+ [self updateBindPath];
+ [self setServerPath:[self getStartPath]];
+
+ [self startServer];
+}
+
+-(NSString *) getStartPath {
NSString * wwwPath = [[NSBundle mainBundle] pathForResource:@"www" ofType: nil];
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
@@ -162,11 +172,8 @@ - (void)initWebServer
NSString * snapshots = [cordovaDataDirectory stringByAppendingPathComponent:@"ionic_built_snapshots"];
wwwPath = [snapshots stringByAppendingPathComponent:[persistedPath lastPathComponent]];
}
-
- [self updateBindPath];
- [self setServerPath:wwwPath];
-
- [self startServer];
+ self.basePath = wwwPath;
+ return wwwPath;
}
-(BOOL) isNewBinary
@@ -264,9 +271,22 @@ - (void)pluginInitialize
{
// viewController would be available now. we attempt to set all possible delegates to it, by default
NSDictionary* settings = self.commandDelegate.settings;
- self.internalConnectionsOnly = [settings cordovaBoolSettingForKey:@"WKInternalConnectionsOnly" defaultValue:YES];
+ if (@available(iOS 11.0, *)) {
+ self.useScheme = [settings cordovaBoolSettingForKey:@"UseScheme" defaultValue:NO];
+ } else {
+ self.useScheme = NO;
+ }
- [self initWebServer];
+ self.internalConnectionsOnly = [settings cordovaBoolSettingForKey:@"WKInternalConnectionsOnly" defaultValue:YES];
+ if (self.useScheme) {
+ NSString *bind = [settings cordovaSettingForKey:@"HostName"];
+ if(bind == nil){
+ bind = @"app";
+ }
+ self.CDV_LOCAL_SERVER = [NSString stringWithFormat:@"ionic://%@", bind];
+ } else {
+ [self initWebServer];
+ }
self.uiDelegate = [[CDVWKWebViewUIDelegate alloc] initWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]];
@@ -307,6 +327,15 @@ - (void)pluginInitialize
WKWebViewConfiguration* configuration = [self createConfigurationFromSettings:settings];
configuration.userContentController = userContentController;
+ if (@available(iOS 11.0, *)) {
+ if (self.useScheme) {
+ self.handler = [[IONAssetHandler alloc] init];
+ [self.handler setAssetPath:[self getStartPath]];
+ [configuration setURLSchemeHandler:self.handler forURLScheme:@"ionic"];
+ [configuration setURLSchemeHandler:self.handler forURLScheme:@"ionic-asset"];
+ }
+ }
+
// re-create WKWebView, since we need to update configuration
// remove from keyWindow before recreating
[self.engineWebView removeFromSuperview];
@@ -459,7 +488,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
if (context == KVOContext) {
if (object == [self webView] && [keyPath isEqualToString: @"URL"] && [object valueForKeyPath:keyPath] == nil){
NSLog(@"URL is nil. Reloading WKWebView");
- if ([self.webServer isRunning]) {
+ if ([self isSafeToReload]) {
[(WKWebView*)_engineWebView reload];
} else {
[self loadErrorPage:nil];
@@ -472,7 +501,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
- (void)onAppWillEnterForeground:(NSNotification *)notification {
if ([self shouldReloadWebView]) {
- if ([self.webServer isRunning]) {
+ if ([self isSafeToReload]) {
NSLog(@"%@", @"CDVWKWebViewEngine reloading!");
[(WKWebView*)_engineWebView reload];
} else {
@@ -516,6 +545,11 @@ - (BOOL)shouldReloadWebView
return [self shouldReloadWebView:wkWebView.URL title:wkWebView.title];
}
+- (BOOL)isSafeToReload
+{
+ return [self.webServer isRunning] || self.useScheme;
+}
+
- (BOOL)shouldReloadWebView:(NSURL *)location title:(NSString*)title
{
BOOL title_is_nil = (title == nil);
@@ -551,7 +585,7 @@ - (id)loadRequest:(NSURLRequest *)request
}
request = [NSURLRequest requestWithURL:url];
}
- if ([self.webServer isRunning]) {
+ if ([self isSafeToReload]) {
return [(WKWebView*)_engineWebView loadRequest:request];
} else {
return [self loadErrorPage:request];
@@ -831,7 +865,7 @@ - (void)webView:(WKWebView*)theWebView didFailNavigation:(WKNavigation*)navigati
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
{
- if ([self.webServer isRunning]) {
+ if ([self isSafeToReload]) {
[webView reload];
} else {
[self loadErrorPage:nil];
@@ -912,9 +946,15 @@ -(void)getServerBasePath:(CDVInvokedUrlCommand*)command
-(void)setServerBasePath:(CDVInvokedUrlCommand*)command
{
NSString * path = [command argumentAtIndex:0];
- [self setServerPath:path];
+ if (self.useScheme) {
+ self.basePath = path;
+ [self.handler setAssetPath:path];
+ } else {
+ [self setServerPath:path];
+ }
+
NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.CDV_LOCAL_SERVER]];
- if ([self.webServer isRunning]) {
+ if ([self isSafeToReload]) {
[(WKWebView*)_engineWebView loadRequest:request];
} else {
[self loadErrorPage:request];
diff --git a/src/ios/IONAssetHandler.h b/src/ios/IONAssetHandler.h
new file mode 100644
index 00000000..61cab670
--- /dev/null
+++ b/src/ios/IONAssetHandler.h
@@ -0,0 +1,10 @@
+#import
+#import
+
+@interface IONAssetHandler : NSObject
+
+@property (nonatomic, strong) NSString * basePath;
+
+-(void)setAssetPath:(NSString *)assetPath;
+
+@end
diff --git a/src/ios/IONAssetHandler.m b/src/ios/IONAssetHandler.m
new file mode 100644
index 00000000..f03db6cd
--- /dev/null
+++ b/src/ios/IONAssetHandler.m
@@ -0,0 +1,74 @@
+#import "IONAssetHandler.h"
+#import
+
+@implementation IONAssetHandler
+
+-(void)setAssetPath:(NSString *)assetPath {
+ self.basePath = assetPath;
+}
+
+- (void)webView:(WKWebView *)webView startURLSchemeTask:(id )urlSchemeTask
+API_AVAILABLE(ios(11.0)){
+ NSString * startPath = @"";
+ NSURL * url = urlSchemeTask.request.URL;
+ NSString * stringToLoad = url.path;
+ NSString * scheme = url.scheme;
+ if ([scheme isEqualToString:@"ionic"]) {
+ startPath = self.basePath;
+ if ([stringToLoad isEqualToString:@""] || !url.pathExtension) {
+ startPath = [startPath stringByAppendingString:@"/index.html"];
+ } else {
+ startPath = [startPath stringByAppendingString:stringToLoad];
+ }
+ } else {
+ if (![stringToLoad isEqualToString:@""]) {
+ startPath = stringToLoad;
+ }
+ }
+
+ NSData * data = [[NSData alloc] initWithContentsOfFile:startPath];
+ NSInteger statusCode = 200;
+ if (!data) {
+ statusCode = 404;
+ }
+ NSURL * localUrl = [NSURL URLWithString:url.absoluteString];
+ NSString * mimeType = [self getMimeType:url.pathExtension];
+ id response = nil;
+ if (data && [self isMediaExtension:url.pathExtension]) {
+ response = [[NSURLResponse alloc] initWithURL:localUrl MIMEType:mimeType expectedContentLength:data.length textEncodingName:nil];
+ } else {
+ NSDictionary * headers = @{ @"Content-Type" : mimeType, @"Cache-Control": @"no-cache"};
+ response = [[NSHTTPURLResponse alloc] initWithURL:localUrl statusCode:statusCode HTTPVersion:nil headerFields:headers];
+ }
+
+ [urlSchemeTask didReceiveResponse:response];
+ [urlSchemeTask didReceiveData:data];
+ [urlSchemeTask didFinish];
+
+}
+
+- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id)urlSchemeTask API_AVAILABLE(ios(11.0)){
+ NSLog(@"stop");
+}
+
+-(NSString *) getMimeType:(NSString *)fileExtension {
+ if (fileExtension && ![fileExtension isEqualToString:@""]) {
+ NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
+ NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
+ return contentType ? contentType : @"application/octet-stream";
+ } else {
+ return @"text/html";
+ }
+}
+
+-(BOOL) isMediaExtension:(NSString *) pathExtension {
+ NSArray * mediaExtensions = @[@"m4v", @"mov", @"mp4",
+ @"aac", @"ac3", @"aiff", @"au", @"flac", @"m4a", @"mp3", @"wav"];
+ if ([mediaExtensions containsObject:pathExtension]) {
+ return YES;
+ }
+ return NO;
+}
+
+
+@end