#import "FlutterWebviewPlugin.h" static NSString *const CHANNEL_NAME = @"flutter_webview_plugin"; // UIWebViewDelegate @interface FlutterWebviewPlugin() { BOOL _enableAppScheme; BOOL _enableZoom; } @end @implementation FlutterWebviewPlugin + (void)registerWithRegistrar:(NSObject*)registrar { channel = [FlutterMethodChannel methodChannelWithName:CHANNEL_NAME binaryMessenger:[registrar messenger]]; UIViewController *viewController = [UIApplication sharedApplication].delegate.window.rootViewController; FlutterWebviewPlugin* instance = [[FlutterWebviewPlugin alloc] initWithViewController:viewController]; [registrar addMethodCallDelegate:instance channel:channel]; } - (instancetype)initWithViewController:(UIViewController *)viewController { self = [super init]; if (self) { self.viewController = viewController; } return self; } - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { if ([@"launch" isEqualToString:call.method]) { if (!self.webview) [self initWebview:call]; else [self navigate:call]; result(nil); } else if ([@"close" isEqualToString:call.method]) { [self closeWebView]; result(nil); } else if ([@"eval" isEqualToString:call.method]) { [self evalJavascript:call completionHandler:^(NSString * response) { result(response); }]; } else if ([@"resize" isEqualToString:call.method]) { [self resize:call]; result(nil); } else if ([@"reloadUrl" isEqualToString:call.method]) { [self reloadUrl:call]; result(nil); } else if ([@"show" isEqualToString:call.method]) { [self show]; result(nil); } else if ([@"hide" isEqualToString:call.method]) { [self hide]; result(nil); } else if ([@"stopLoading" isEqualToString:call.method]) { [self stopLoading]; result(nil); } else if ([@"back" isEqualToString:call.method]) { [self back]; result(nil); } else if ([@"forward" isEqualToString:call.method]) { [self forward]; result(nil); } else if ([@"reload" isEqualToString:call.method]) { [self reload]; result(nil); } else { result(FlutterMethodNotImplemented); } } - (void)initWebview:(FlutterMethodCall*)call { NSNumber *clearCache = call.arguments[@"clearCache"]; NSNumber *clearCookies = call.arguments[@"clearCookies"]; NSNumber *hidden = call.arguments[@"hidden"]; NSDictionary *rect = call.arguments[@"rect"]; _enableAppScheme = call.arguments[@"enableAppScheme"]; NSString *userAgent = call.arguments[@"userAgent"]; NSNumber *withZoom = call.arguments[@"withZoom"]; NSNumber *scrollBar = call.arguments[@"scrollBar"]; if (clearCache != (id)[NSNull null] && [clearCache boolValue]) { [[NSURLCache sharedURLCache] removeAllCachedResponses]; } if (clearCookies != (id)[NSNull null] && [clearCookies boolValue]) { [[NSURLSession sharedSession] resetWithCompletionHandler:^{ }]; } if (userAgent != (id)[NSNull null]) { [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent": userAgent}]; } CGRect rc; if (rect != nil) { rc = [self parseRect:rect]; } else { rc = self.viewController.view.bounds; } self.webview = [[WKWebView alloc] initWithFrame:rc]; self.webview.navigationDelegate = self; self.webview.scrollView.delegate = self; self.webview.hidden = [hidden boolValue]; self.webview.scrollView.showsHorizontalScrollIndicator = [scrollBar boolValue]; self.webview.scrollView.showsVerticalScrollIndicator = [scrollBar boolValue]; _enableZoom = [withZoom boolValue]; [self.viewController.view addSubview:self.webview]; [self navigate:call]; } - (CGRect)parseRect:(NSDictionary *)rect { return CGRectMake([[rect valueForKey:@"left"] doubleValue], [[rect valueForKey:@"top"] doubleValue], [[rect valueForKey:@"width"] doubleValue], [[rect valueForKey:@"height"] doubleValue]); } - (void) scrollViewDidScroll:(UIScrollView *)scrollView { id xDirection = @{@"xDirection": @(scrollView.contentOffset.x) }; [channel invokeMethod:@"onScrollXChanged" arguments:xDirection]; id yDirection = @{@"yDirection": @(scrollView.contentOffset.y) }; [channel invokeMethod:@"onScrollYChanged" arguments:yDirection]; } - (void)navigate:(FlutterMethodCall*)call { if (self.webview != nil) { NSString *url = call.arguments[@"url"]; NSNumber *withLocalUrl = call.arguments[@"withLocalUrl"]; if ( [withLocalUrl boolValue]) { NSURL *htmlUrl = [NSURL fileURLWithPath:url isDirectory:false]; if (@available(iOS 9.0, *)) { [self.webview loadFileURL:htmlUrl allowingReadAccessToURL:htmlUrl]; } else { @throw @"not available on version earlier than ios 9.0"; } } else { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]]; NSDictionary *headers = call.arguments[@"headers"]; if (headers != nil) { [request setAllHTTPHeaderFields:headers]; } [self.webview loadRequest:request]; } } } - (void)evalJavascript:(FlutterMethodCall*)call completionHandler:(void (^_Nullable)(NSString * response))completionHandler { if (self.webview != nil) { NSString *code = call.arguments[@"code"]; [self.webview evaluateJavaScript:code completionHandler:^(id _Nullable response, NSError * _Nullable error) { completionHandler([NSString stringWithFormat:@"%@", response]); }]; } else { completionHandler(nil); } } - (void)resize:(FlutterMethodCall*)call { if (self.webview != nil) { NSDictionary *rect = call.arguments[@"rect"]; CGRect rc = [self parseRect:rect]; self.webview.frame = rc; } } - (void)closeWebView { if (self.webview != nil) { [self.webview stopLoading]; [self.webview removeFromSuperview]; self.webview.navigationDelegate = nil; self.webview = nil; // manually trigger onDestroy [channel invokeMethod:@"onDestroy" arguments:nil]; } } - (void)reloadUrl:(FlutterMethodCall*)call { if (self.webview != nil) { NSString *url = call.arguments[@"url"]; NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; [self.webview loadRequest:request]; } } - (void)show { if (self.webview != nil) { self.webview.hidden = false; } } - (void)hide { if (self.webview != nil) { self.webview.hidden = true; } } - (void)stopLoading { if (self.webview != nil) { [self.webview stopLoading]; } } - (void)back { if (self.webview != nil) { [self.webview goBack]; } } - (void)forward { if (self.webview != nil) { [self.webview goForward]; } } - (void)reload { if (self.webview != nil) { [self.webview reload]; } } #pragma mark -- WkWebView Delegate - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { id data = @{@"url": navigationAction.request.URL.absoluteString, @"type": @"shouldStart", @"navigationType": [NSNumber numberWithInt:navigationAction.navigationType]}; [channel invokeMethod:@"onState" arguments:data]; if (navigationAction.navigationType == WKNavigationTypeBackForward) { [channel invokeMethod:@"onBackPressed" arguments:nil]; } else { id data = @{@"url": navigationAction.request.URL.absoluteString}; [channel invokeMethod:@"onUrlChanged" arguments:data]; } if (_enableAppScheme || ([webView.URL.scheme isEqualToString:@"http"] || [webView.URL.scheme isEqualToString:@"https"] || [webView.URL.scheme isEqualToString:@"about"])) { decisionHandler(WKNavigationActionPolicyAllow); } else { decisionHandler(WKNavigationActionPolicyCancel); } } - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation { [channel invokeMethod:@"onState" arguments:@{@"type": @"startLoad", @"url": webView.URL.absoluteString}]; } - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { [channel invokeMethod:@"onState" arguments:@{@"type": @"finishLoad", @"url": webView.URL.absoluteString}]; } - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error { id data = [FlutterError errorWithCode:[NSString stringWithFormat:@"%ld", error.code] message:error.localizedDescription details:error.localizedFailureReason]; [channel invokeMethod:@"onError" arguments:data]; } - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler { if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) { NSHTTPURLResponse * response = (NSHTTPURLResponse *)navigationResponse.response; [channel invokeMethod:@"onHttpError" arguments:@{@"code": [NSString stringWithFormat:@"%ld", response.statusCode], @"url": webView.URL.absoluteString}]; } decisionHandler(WKNavigationResponsePolicyAllow); } #pragma mark -- UIScrollViewDelegate - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { if (scrollView.pinchGestureRecognizer.isEnabled != _enableZoom) { scrollView.pinchGestureRecognizer.enabled = _enableZoom; } } @end