Explorar el Código

feat(ios): expose UIViewController (#6281)

Lucas Fernandes Nogueira hace 2 años
padre
commit
d42fd0710c

+ 1 - 1
core/tauri-build/src/mobile.rs

@@ -118,7 +118,7 @@ pub fn link_swift_library(name: &str, source: impl AsRef<Path>) {
   let curr_dir = std::env::current_dir().unwrap();
   std::env::set_current_dir(&source).unwrap();
   swift_rs::build::SwiftLinker::new("10.13")
-    .with_ios("11")
+    .with_ios("13.0")
     .with_package(name, source)
     .link();
   std::env::set_current_dir(&curr_dir).unwrap();

+ 3 - 1
core/tauri-runtime-wry/src/lib.rs

@@ -2385,10 +2385,12 @@ fn handle_user_message<T: UserEvent>(
                 }
                 #[cfg(target_os = "ios")]
                 {
-                  use wry::webview::WebviewExtIOS;
+                  use wry::{application::platform::ios::WindowExtIOS, webview::WebviewExtIOS};
+
                   f(Webview {
                     webview: w.webview(),
                     manager: w.manager(),
+                    view_controller: w.window().ui_view_controller() as cocoa::base::id,
                   });
                 }
                 #[cfg(windows)]

+ 1 - 0
core/tauri-runtime-wry/src/webview.rs

@@ -33,6 +33,7 @@ mod imp {
   pub struct Webview {
     pub webview: id,
     pub manager: id,
+    pub view_controller: id,
   }
 }
 

+ 10 - 2
core/tauri/mobile/ios-api/Sources/Tauri/Invoke.swift

@@ -1,5 +1,5 @@
 import Foundation
-import MetalKit
+import UIKit
 
 @objc public class Invoke: NSObject, JSValueContainer, BridgedJSValueContainer {
 	public var dictionaryRepresentation: NSDictionary {
@@ -18,7 +18,15 @@ import MetalKit
 		self.data = data ?? [:]
 	}
 
-	public func resolve(_ data: JsonValue? = nil) {
+	public func resolve() {
+		sendResponse(nil, nil)
+	}
+
+	public func resolve(_ data: JsonObject) {
+		resolve(.dictionary(data))
+	}
+
+	public func resolve(_ data: JsonValue) {
 		sendResponse(data, nil)
 	}
 

+ 3 - 1
core/tauri/mobile/ios-api/Sources/Tauri/Plugin/Plugin.swift

@@ -2,6 +2,8 @@ import WebKit
 import os.log
 
 open class Plugin: NSObject {
+    public let manager: PluginManager = PluginManager.shared
+
     @objc open func load(webview: WKWebView) {}
 
     @objc open func checkPermissions(_ invoke: Invoke) {
@@ -10,5 +12,5 @@ open class Plugin: NSObject {
 
     @objc open func requestPermissions(_ invoke: Invoke) {
         invoke.resolve()
-    }   
+    }
 }

+ 41 - 21
core/tauri/mobile/ios-api/Sources/Tauri/Tauri.swift

@@ -1,6 +1,6 @@
 import SwiftRs
 import Foundation
-import MetalKit
+import UIKit
 import WebKit
 import os.log
 
@@ -13,9 +13,26 @@ class PluginHandle {
 	}
 }
 
-class PluginManager {
-	static var shared: PluginManager = PluginManager()
+public class PluginManager {
+	static let shared: PluginManager = PluginManager()
+	public var viewController: UIViewController?
 	var plugins: [String: PluginHandle] = [:]
+	var ipcDispatchQueue = DispatchQueue(label: "ipc")
+	public var isSimEnvironment: Bool {
+		#if targetEnvironment(simulator)
+		return true
+		#else
+		return false
+		#endif
+	}
+
+	public func assetUrl(fromLocalURL url: URL?) -> URL? {
+		guard let inputURL = url else {
+			return nil
+		}
+
+		return URL(string: "asset://localhost")!.appendingPathComponent(inputURL.path)
+	}
 
 	func onWebviewCreated(_ webview: WKWebView) {
 		for (_, handle) in plugins {
@@ -36,23 +53,25 @@ class PluginManager {
 
 	func invoke(name: String, methodName: String, invoke: Invoke) {
 		if let plugin = plugins[name] {
-			let selectorWithThrows = Selector(("\(methodName):error:"))
-			if plugin.instance.responds(to: selectorWithThrows) {
-				var error: NSError? = nil
-				withUnsafeMutablePointer(to: &error) {
-					let methodIMP: IMP! = plugin.instance.method(for: selectorWithThrows)
-					unsafeBitCast(methodIMP, to: (@convention(c)(Any?, Selector, Invoke, OpaquePointer) -> Void).self)(plugin, selectorWithThrows, invoke, OpaquePointer($0))
-				}
-				if let error = error {
-					invoke.reject("\(error)")
-					let _ = toRust(error) // TODO app is crashing without this memory leak (when an error is thrown)
-				}
-			} else {
-				let selector = Selector(("\(methodName):"))
-				if plugin.instance.responds(to: selector) {
-					plugin.instance.perform(selector, with: invoke)
+			ipcDispatchQueue.async {
+				let selectorWithThrows = Selector(("\(methodName):error:"))
+				if plugin.instance.responds(to: selectorWithThrows) {
+					var error: NSError? = nil
+					withUnsafeMutablePointer(to: &error) {
+						let methodIMP: IMP! = plugin.instance.method(for: selectorWithThrows)
+						unsafeBitCast(methodIMP, to: (@convention(c)(Any?, Selector, Invoke, OpaquePointer) -> Void).self)(plugin, selectorWithThrows, invoke, OpaquePointer($0))
+					}
+					if let error = error {
+						invoke.reject("\(error)")
+						let _ = toRust(error) // TODO app is crashing without this memory leak (when an error is thrown)
+					}
 				} else {
-					invoke.reject("No method \(methodName) found for plugin \(name)")
+					let selector = Selector(("\(methodName):"))
+					if plugin.instance.responds(to: selector) {
+						plugin.instance.perform(selector, with: invoke)
+					} else {
+						invoke.reject("No method \(methodName) found for plugin \(name)")
+					}
 				}
 			}
 		} else {
@@ -62,7 +81,7 @@ class PluginManager {
 }
 
 extension PluginManager: NSCopying {
-	func copy(with zone: NSZone? = nil) -> Any {
+	public func copy(with zone: NSZone? = nil) -> Any {
 		return self
 	}
 }
@@ -76,7 +95,8 @@ public func registerPlugin<P: Plugin>(webview: WKWebView?, name: String, plugin:
 }
 
 @_cdecl("on_webview_created")
-func onWebviewCreated(webview: WKWebView) {
+func onWebviewCreated(webview: WKWebView, viewController: UIViewController) {
+	PluginManager.shared.viewController = viewController
 	PluginManager.shared.onWebviewCreated(webview)
 }
 

+ 11 - 0
core/tauri/mobile/ios-api/Sources/Tauri/UiUtils.swift

@@ -0,0 +1,11 @@
+import UIKit
+
+public class UIUtils {
+    public static func centerPopover(rootViewController: UIViewController?, popoverController: UIViewController) {
+        if let viewController = rootViewController {
+            popoverController.popoverPresentationController?.sourceRect = CGRectMake(viewController.view.center.x, viewController.view.center.y, 0, 0)
+            popoverController.popoverPresentationController?.sourceView = viewController.view
+            popoverController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up
+        }
+    }
+}

+ 1 - 1
core/tauri/src/ios.rs

@@ -25,7 +25,7 @@ extern "C" {
     callback: PluginMessageCallback,
   );
 
-  pub fn on_webview_created(webview: id);
+  pub fn on_webview_created(webview: id, controller: id);
 }
 
 pub fn json_to_dictionary(json: JsonValue) -> id {

+ 1 - 1
core/tauri/src/manager.rs

@@ -1408,7 +1408,7 @@ impl<R: Runtime> WindowManager<R> {
     {
       window
         .with_webview(|w| {
-          unsafe { crate::ios::on_webview_created(w.inner()) };
+          unsafe { crate::ios::on_webview_created(w.inner(), w.view_controller()) };
         })
         .expect("failed to run on_webview_created hook");
     }

+ 9 - 0
core/tauri/src/window.rs

@@ -845,6 +845,15 @@ impl PlatformWebview {
     self.0.ns_window
   }
 
+  /// Returns [UIViewController] used by the WKWebView webview NSWindow.
+  ///
+  /// [UIViewController]: https://developer.apple.com/documentation/uikit/uiviewcontroller
+  #[cfg(target_os = "ios")]
+  #[cfg_attr(doc_cfg, doc(cfg(target_os = "ios")))]
+  pub fn view_controller(&self) -> cocoa::base::id {
+    self.0.view_controller
+  }
+
   /// Returns handle for JNI execution.
   #[cfg(target_os = "android")]
   pub fn jni_handle(&self) -> tauri_runtime_wry::wry::webview::JniHandle {

+ 1 - 1
examples/api/src-tauri/tauri-plugin-sample/ios/Package.swift

@@ -6,7 +6,7 @@ import PackageDescription
 let package = Package(
     name: "tauri-plugin-sample",
     platforms: [
-        .iOS(.v11),
+        .iOS(.v13),
     ],
     products: [
         // Products define the executables and libraries a package produces, and make them visible to other packages.

+ 2 - 2
examples/api/src-tauri/tauri-plugin-sample/ios/Sources/ExamplePlugin.swift

@@ -1,11 +1,11 @@
-import MetalKit
+import UIKit
 import WebKit
 import Tauri
 
 class ExamplePlugin: Plugin {
 	@objc public func ping(_ invoke: Invoke) throws {
 		let value = invoke.getString("value")
-		invoke.resolve(.dictionary(["value": value as Any]))
+		invoke.resolve(["value": value as Any])
 	}
 }
 

+ 2 - 0
tooling/cli/src/mobile/ios.rs

@@ -46,6 +46,7 @@ pub(crate) mod project;
 mod xcode_script;
 
 pub const APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME: &str = "TAURI_APPLE_DEVELOPMENT_TEAM";
+const TARGET_IOS_VERSION: &str = "13.0";
 
 #[derive(Parser)]
 #[clap(
@@ -124,6 +125,7 @@ pub fn get_config(
     ios_features: ios_options.features.clone(),
     bundle_version: config.package.version.clone(),
     bundle_version_short: config.package.version.clone(),
+    ios_version: Some(TARGET_IOS_VERSION.into()),
     ..Default::default()
   };
   let config = AppleConfig::from_raw(app.clone(), Some(raw)).unwrap();

+ 1 - 1
tooling/cli/templates/plugin/ios/Package.swift

@@ -6,7 +6,7 @@ import PackageDescription
 let package = Package(
     name: "tauri-plugin-{{ plugin_name }}",
     platforms: [
-        .iOS(.v11),
+        .iOS(.v13),
     ],
     products: [
         // Products define the executables and libraries a package produces, and make them visible to other packages.

+ 2 - 2
tooling/cli/templates/plugin/ios/Sources/ExamplePlugin.swift

@@ -1,11 +1,11 @@
-import MetalKit
+import UIKit
 import WebKit
 import Tauri
 
 class ExamplePlugin: Plugin {
 	@objc public func ping(_ invoke: Invoke) throws {
 		let value = invoke.getString("value")
-		invoke.resolve(.dictionary(["value": value as Any]))
+		invoke.resolve(["value": value as Any])
 	}
 }