Allow specifying a navigation delegate (iOS implementation). (#1323)

This is the iOS implementation of the navigation delegate method channel.

The Dart and Android implementations are in #1236

Splitting off the iOS to keep a reasonable change size.
This PR will be merged first.

flutter/flutter#25329
diff --git a/packages/webview_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/webview_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/webview_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/webview_flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/webview_flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..949b678
--- /dev/null
+++ b/packages/webview_flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>BuildSystemType</key>
+	<string>Original</string>
+</dict>
+</plist>
diff --git a/packages/webview_flutter/ios/Classes/FLTWKNavigationDelegate.h b/packages/webview_flutter/ios/Classes/FLTWKNavigationDelegate.h
new file mode 100644
index 0000000..1625c49
--- /dev/null
+++ b/packages/webview_flutter/ios/Classes/FLTWKNavigationDelegate.h
@@ -0,0 +1,21 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Flutter/Flutter.h>
+#import <WebKit/WebKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FLTWKNavigationDelegate : NSObject <WKNavigationDelegate>
+
+- (instancetype)initWithChannel:(FlutterMethodChannel*)channel;
+
+/**
+ * Whether to delegate navigation decisions over the method channel.
+ */
+@property(nonatomic, assign) BOOL hasDartNavigationDelegate;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/webview_flutter/ios/Classes/FLTWKNavigationDelegate.m b/packages/webview_flutter/ios/Classes/FLTWKNavigationDelegate.m
new file mode 100644
index 0000000..3638ddc
--- /dev/null
+++ b/packages/webview_flutter/ios/Classes/FLTWKNavigationDelegate.m
@@ -0,0 +1,59 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "FLTWKNavigationDelegate.h"
+
+@implementation FLTWKNavigationDelegate {
+  FlutterMethodChannel* _methodChannel;
+}
+
+- (instancetype)initWithChannel:(FlutterMethodChannel*)channel {
+  self = [super init];
+  if (self) {
+    _methodChannel = channel;
+  }
+  return self;
+}
+
+- (void)webView:(WKWebView*)webView
+    decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction
+                    decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
+  if (!self.hasDartNavigationDelegate) {
+    decisionHandler(WKNavigationActionPolicyAllow);
+    return;
+  }
+  NSDictionary* arguments = @{
+    @"url" : navigationAction.request.URL.absoluteString,
+    @"isForMainFrame" : @(navigationAction.targetFrame.isMainFrame)
+  };
+  [_methodChannel invokeMethod:@"navigationRequest"
+                     arguments:arguments
+                        result:^(id _Nullable result) {
+                          if ([result isKindOfClass:[FlutterError class]]) {
+                            NSLog(@"navigationRequest has unexpectedly completed with an error, "
+                                  @"allowing navigation.");
+                            decisionHandler(WKNavigationActionPolicyAllow);
+                            return;
+                          }
+                          if (result == FlutterMethodNotImplemented) {
+                            NSLog(@"navigationRequest was unexepectedly not implemented: %@, "
+                                  @"allowing navigation.",
+                                  result);
+                            decisionHandler(WKNavigationActionPolicyAllow);
+                            return;
+                          }
+                          if (![result isKindOfClass:[NSNumber class]]) {
+                            NSLog(@"navigationRequest unexpectedly returned a non boolean value: "
+                                  @"%@, allowing navigation.",
+                                  result);
+                            decisionHandler(WKNavigationActionPolicyAllow);
+                            return;
+                          }
+                          NSNumber* typedResult = result;
+                          decisionHandler([typedResult boolValue] ? WKNavigationActionPolicyAllow
+                                                                  : WKNavigationActionPolicyCancel);
+                        }];
+}
+
+@end
diff --git a/packages/webview_flutter/ios/Classes/FlutterWebView.m b/packages/webview_flutter/ios/Classes/FlutterWebView.m
index 87d4d25..ea3b192 100644
--- a/packages/webview_flutter/ios/Classes/FlutterWebView.m
+++ b/packages/webview_flutter/ios/Classes/FlutterWebView.m
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #import "FlutterWebView.h"
+#import "FLTWKNavigationDelegate.h"
 #import "JavaScriptChannelHandler.h"
 
 @implementation FLTWebViewFactory {
@@ -40,6 +41,7 @@
   NSString* _currentUrl;
   // The set of registered JavaScript channel names.
   NSMutableSet* _javaScriptChannelNames;
+  FLTWKNavigationDelegate* _navigationDelegate;
 }
 
 - (instancetype)initWithFrame:(CGRect)frame
@@ -64,6 +66,8 @@
     configuration.userContentController = userContentController;
 
     _webView = [[WKWebView alloc] initWithFrame:frame configuration:configuration];
+    _navigationDelegate = [[FLTWKNavigationDelegate alloc] initWithChannel:_channel];
+    _webView.navigationDelegate = _navigationDelegate;
     __weak __typeof__(self) weakSelf = self;
     [_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
       [weakSelf onMethodCall:call result:result];
@@ -229,6 +233,9 @@
     if ([key isEqualToString:@"jsMode"]) {
       NSNumber* mode = settings[key];
       [self updateJsMode:mode];
+    } else if ([key isEqualToString:@"hasNavigationDelegate"]) {
+      NSNumber* hasDartNavigationDelegate = settings[key];
+      _navigationDelegate.hasDartNavigationDelegate = [hasDartNavigationDelegate boolValue];
     } else {
       NSLog(@"webview_flutter: unknown setting key: %@", key);
     }