瀏覽代碼

iOS: add delegate

Ken 8 年之前
父節點
當前提交
1e59c1830b

+ 0 - 13
example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java

@@ -1,13 +0,0 @@
-package io.flutter.plugins;
-
-import io.flutter.plugin.common.PluginRegistry;
-import com.flutter_webview_plugin.FlutterWebviewPlugin;
-
-/**
- * Generated file. Do not edit.
- */
-public final class GeneratedPluginRegistrant {
-  public static void registerWith(PluginRegistry registry) {
-    FlutterWebviewPlugin.registerWith(registry.registrarFor("com.flutter_webview_plugin.FlutterWebviewPlugin"));
-  }
-}

+ 9 - 1
example/ios/Runner.xcodeproj/project.pbxproj

@@ -257,9 +257,14 @@
 			files = (
 			);
 			inputPaths = (
+				"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
+				"${PODS_ROOT}/../../../../../flutter/bin/cache/artifacts/engine/ios/Flutter.framework",
+				"${BUILT_PRODUCTS_DIR}/flutter_webview_plugin/flutter_webview_plugin.framework",
 			);
 			name = "[CP] Embed Pods Frameworks";
 			outputPaths = (
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_webview_plugin.framework",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
@@ -286,13 +291,16 @@
 			files = (
 			);
 			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
 			);
 			name = "[CP] Check Pods Manifest.lock";
 			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n";
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
 			showEnvVarsInLog = 0;
 		};
 /* End PBXShellScriptBuildPhase section */

+ 23 - 11
example/lib/main.dart

@@ -40,8 +40,10 @@ class _MyHomePageState extends State<MyHomePage> {
   // On urlChanged stream
   StreamSubscription<String> _onUrlChanged;
 
+  StreamSubscription<String> _onStateChanged;
+
   TextEditingController _ctrl =
-  new TextEditingController(text: "https://flutter.io");
+      new TextEditingController(text: "https://flutter.io");
   GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey();
 
   final _history = [];
@@ -50,6 +52,14 @@ class _MyHomePageState extends State<MyHomePage> {
   initState() {
     super.initState();
 
+    _onStateChanged = flutterWebviewPlugin.stateChanged.listen((String state) {
+      if (mounted) {
+        setState(() {
+          _history.add(state);
+        });
+      }
+    });
+
     // Add a listener to on destroy WebView, so you can make came actions.
     _onDestroy = flutterWebviewPlugin.onDestroy.listen((_) {
       if (mounted) {
@@ -93,21 +103,23 @@ class _MyHomePageState extends State<MyHomePage> {
             child: new TextField(controller: _ctrl),
           ),
           new RaisedButton(
-            onPressed: _onPressed,
+            onPressed: () {
+              flutterWebviewPlugin.launch(_ctrl.text,
+                  fullScreen: false,
+                  rect: new Rect.fromLTWH(
+                      0.0, 0.0, MediaQuery.of(context).size.width, 300.0));
+            },
             child: new Text("Open Webview"),
           ),
+          new RaisedButton(
+            onPressed: () {
+              flutterWebviewPlugin.launch(_ctrl.text, fullScreen: true);
+            },
+            child: new Text("Open Fullscreen Webview"),
+          ),
           new Text(_history.join(", "))
         ],
       ),
     );
   }
-
-  void _onPressed() {
-    try {
-      // This way you launch WebView with an url as a parameter.
-      flutterWebviewPlugin.launch(_ctrl.text);
-    } catch (e) {
-      print(e);
-    }
-  }
 }

+ 1 - 1
ios/Classes/FlutterWebviewPlugin.h

@@ -5,5 +5,5 @@ static FlutterMethodChannel *channel;
 
 @interface FlutterWebviewPlugin : NSObject<FlutterPlugin>
 @property (nonatomic, retain) UIViewController *viewController;
-@property (nonatomic, retain) WebviewController *webviewController;
+@property (nonatomic, retain) UIWebView *webview;
 @end

+ 99 - 12
ios/Classes/FlutterWebviewPlugin.m

@@ -1,15 +1,29 @@
 #import "FlutterWebviewPlugin.h"
 
 static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
+static NSString *const EVENT_CHANNEL_NAME = @"flutter_webview_plugin_event";
+
+// UIWebViewDelegate
+@interface FlutterWebviewPlugin() <UIWebViewDelegate, FlutterStreamHandler> {
+    FlutterEventSink _eventSink;
+}
+@end
 
 @implementation FlutterWebviewPlugin
 + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
     channel = [FlutterMethodChannel
                methodChannelWithName:CHANNEL_NAME
                binaryMessenger:[registrar messenger]];
+    
     UIViewController *viewController = (UIViewController *)registrar.messenger;
     FlutterWebviewPlugin* instance = [[FlutterWebviewPlugin alloc] initWithViewController:viewController];
+    
     [registrar addMethodCallDelegate:instance channel:channel];
+    
+    FlutterEventChannel* event =
+    [FlutterEventChannel eventChannelWithName:EVENT_CHANNEL_NAME
+                              binaryMessenger:[registrar messenger]];
+    [event setStreamHandler:instance];
 }
 
 - (instancetype)initWithViewController:(UIViewController *)viewController {
@@ -22,7 +36,10 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
 
 - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
     if ([@"launch" isEqualToString:call.method]) {
-        [self showWebView:call];
+        if (!self.webview)
+            [self initWebView:call];
+        else
+            [self launch:call];
         result(nil);
     } else if ([@"close" isEqualToString:call.method]) {
         [self closeWebView];
@@ -32,27 +49,97 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
     }
 }
 
-- (void)showWebView:(FlutterMethodCall*)call {
+- (void)launch:(FlutterMethodCall*)call {
     NSString *url = call.arguments[@"url"];
-    NSNumber *withJavascript = call.arguments[@"withJavascript"];
+    
+    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
+    [self.webview loadRequest:request];
+}
+
+- (void)initWebView:(FlutterMethodCall*)call {
+    // NSNumber *withJavascript = call.arguments[@"withJavascript"];
     NSNumber *clearCache = call.arguments[@"clearCache"];
     NSNumber *clearCookies = call.arguments[@"clearCookies"];
-    NSNumber *fullScreen = call.arguments[@"fullScreen"];
+    NSNumber *hidden = call.arguments[@"hidden"];
+    NSDictionary *rect = call.arguments[@"rect"];
+    
+    //
+    if ([clearCache boolValue]) {
+        [[NSURLCache sharedURLCache] removeAllCachedResponses];
+    }
     
-    self.webviewController = [[WebviewController alloc] initWithUrl:url withJavascript:withJavascript clearCache:clearCache clearCookes:clearCookies];
+    if ([clearCookies boolValue]) {
+        [[NSURLSession sharedSession] resetWithCompletionHandler:^{
+        }];
+    }
     
-    if ([fullScreen boolValue]) {
-        [self.viewController presentViewController:self.webviewController animated:YES completion:nil];
+    CGRect rc;
+    if (rect) {
+        rc = CGRectMake([[rect valueForKey:@"left"] doubleValue],
+                               [[rect valueForKey:@"top"] doubleValue],
+                               [[rect valueForKey:@"width"] doubleValue],
+                               [[rect valueForKey:@"height"] doubleValue]);
     } else {
-        UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:self.webviewController];
-        [self.viewController presentModalViewController:navigation animated:YES];
+        rc = self.viewController.view.bounds;
     }
+    
+    self.webview = [[UIWebView alloc] initWithFrame:rc];
+    self.webview.delegate = self;
+    
+    if (!hidden || ![hidden boolValue])
+        [self.viewController.view addSubview:self.webview];
+    
+    [self launch:call];
 }
 
 - (void)closeWebView {
-    [self.webviewController dismissViewControllerAnimated:YES completion:^{
-        [channel invokeMethod:@"onDestroy" arguments:nil];
-    }];
+    [self.webview stopLoading];
+    [self.webview removeFromSuperview];
+    self.webview.delegate = nil;
+    self.webview = nil;
 }
 
+
+#pragma mark -- WebView Delegate
+- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
+    [self sendStateEvent:[NSString stringWithFormat:@"shouldStart %@", request.URL]];
+    return YES;
+}
+-(void)webViewDidStartLoad:(UIWebView *)webView {
+    [self sendStateEvent:@"startLoad"];
+}
+
+- (void)webViewDidFinishLoad:(UIWebView *)webView {
+    [self sendStateEvent:@"finishLoad"];
+}
+
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
+    id data = [FlutterError errorWithCode:[NSString stringWithFormat:@"%ld", error.code]
+                                  message:error.localizedDescription
+                                  details:error.localizedFailureReason];
+    [self sendStateEvent:data];
+}
+
+#pragma mark -- WkWebView Delegate
+
+#pragma mark -- FlutterStreamHandler impl
+
+- (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink {
+    _eventSink = eventSink;
+    return nil;
+}
+
+- (FlutterError*)onCancelWithArguments:(id)arguments {
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+    _eventSink = nil;
+    return nil;
+}
+
+- (void)sendStateEvent:(id)data {
+    // data should be @"" or [FlutterError]
+    if (!_eventSink)
+        return;
+    
+    _eventSink(data);
+}
 @end

+ 43 - 21
lib/flutter_webview_plugin.dart

@@ -1,33 +1,41 @@
 import 'dart:async';
 
 import 'package:flutter/services.dart';
+import 'package:flutter/material.dart';
 
 const _kChannel = 'flutter_webview_plugin';
+const _kEvent = 'flutter_webview_plugin_event';
 
 /// Singleton Class that communicate with a fullscreen Webview Instance
 /// Have to be instanciate after `runApp` called.
 class FlutterWebviewPlugin {
-  final MethodChannel _channel = const MethodChannel(_kChannel);
+  final MethodChannel _channel;
+
+  final EventChannel _event;
+  Stream<String> _stateChanged;
+
+  Stream<String> get stateChanged {
+    if (_stateChanged == null) {
+      _stateChanged = _event.receiveBroadcastStream();
+    }
+    return _stateChanged;
+  }
+
   final StreamController<Null> _onDestroy = new StreamController.broadcast();
   final StreamController<Null> _onBackPressed =
       new StreamController.broadcast();
 
-  /// TODO: iOS implementation
   final StreamController<String> _onUrlChanged =
       new StreamController.broadcast();
 
-  static FlutterWebviewPlugin _instance;
-  FlutterWebviewPlugin._() {
-    _init();
-  }
-
-  factory FlutterWebviewPlugin() => _instance ??= new FlutterWebviewPlugin._();
-
-  _init() {
+  FlutterWebviewPlugin() : 
+    _channel = const MethodChannel(_kChannel),
+    _event = const EventChannel(_kEvent) {
     _channel.setMethodCallHandler(_handleMessages);
   }
 
   Future<Null> _handleMessages(MethodCall call) async {
+    print("_handleMessages $call");
     switch (call.method) {
       case "onDestroy":
         _onDestroy.add(null);
@@ -56,17 +64,31 @@ class FlutterWebviewPlugin {
   /// - [clearCache] clear the cache of the Webview
   /// - clearCookies] clear all cookies of the Webview
   Future<Null> launch(String url,
-          {bool withJavascript: true,
-          bool clearCache: false,
-          bool clearCookies: false,
-          bool fullScreen: true}) =>
-      _channel.invokeMethod('launch', {
-        "url": url,
-        "withJavascript": withJavascript,
-        "clearCache": clearCache,
-        "clearCookies": clearCookies,
-        "fullScreen": fullScreen
-      });
+      {bool withJavascript: true,
+      bool clearCache: false,
+      bool clearCookies: false,
+      bool hidden: false,
+      bool fullScreen: true,
+      Rect rect: null}) async {
+    Map<String, dynamic> args = {
+      "url": url,
+      "withJavascript": withJavascript,
+      "clearCache": clearCache,
+      "hidden": hidden,
+      "clearCookies": clearCookies,
+      "fullScreen": fullScreen
+    };
+    if (!fullScreen) assert(rect != null);
+    if (rect != null) {
+      args["rect"] = {
+        "left": rect.left,
+        "right": rect.right,
+        "width": rect.width,
+        "height": rect.height
+      };
+    }
+    await _channel.invokeMethod('launch', args);
+  }
 
   /// Close the Webview
   /// Will trigger the [onDestroy] event