The Webview Univeral Flutter package for Appeto SDK.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 
 

250 lignes
8.0 KiB

  1. //
  2. // WebViewLayoutController.swift
  3. // webview_universal
  4. //
  5. // Created by Bin Yang on 2021/11/18.
  6. //
  7. import Cocoa
  8. import FlutterMacOS
  9. import WebKit
  10. class WebViewLayoutController: NSViewController {
  11. private lazy var titleBarController: FlutterViewController = {
  12. let project = FlutterDartProject()
  13. project.dartEntrypointArguments = ["web_view_title_bar", "\(viewId)", "\(titleBarTopPadding)"]
  14. return FlutterViewController(project: project)
  15. }()
  16. private lazy var webView: WKWebView = {
  17. WKWebView()
  18. }()
  19. private var javaScriptHandlerNames: [String] = []
  20. weak var webViewPlugin: WebviewUniversalPlugin?
  21. private var defaultUserAgent: String?
  22. private let methodChannel: FlutterMethodChannel
  23. private let viewId: Int64
  24. private let titleBarHeight: Int
  25. private let titleBarTopPadding: Int
  26. public init(methodChannel: FlutterMethodChannel, viewId: Int64, titleBarHeight: Int, titleBarTopPadding: Int) {
  27. self.viewId = viewId
  28. self.methodChannel = methodChannel
  29. self.titleBarHeight = titleBarHeight
  30. self.titleBarTopPadding = titleBarTopPadding
  31. super.init(nibName: "WebViewLayoutController", bundle: Bundle(for: WebViewLayoutController.self))
  32. }
  33. required init?(coder: NSCoder) {
  34. fatalError("init(coder:) has not been implemented")
  35. }
  36. override func loadView() {
  37. super.loadView()
  38. addChild(titleBarController)
  39. titleBarController.view.translatesAutoresizingMaskIntoConstraints = false
  40. // Register titlebar plugins
  41. ClientMessageChannelPlugin.register(with: titleBarController.registrar(forPlugin: "WebviewUniversalPlugin"))
  42. let flutterView = titleBarController.view
  43. flutterView.translatesAutoresizingMaskIntoConstraints = false
  44. view.addSubview(flutterView)
  45. let constraints = [
  46. flutterView.topAnchor.constraint(equalTo: view.topAnchor),
  47. flutterView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
  48. flutterView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
  49. flutterView.heightAnchor.constraint(equalToConstant: CGFloat(titleBarHeight + titleBarTopPadding)),
  50. ]
  51. NSLayoutConstraint.activate(constraints)
  52. view.addSubview(webView)
  53. webView.translatesAutoresizingMaskIntoConstraints = false
  54. NSLayoutConstraint.activate([
  55. webView.topAnchor.constraint(equalTo: flutterView.bottomAnchor),
  56. webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
  57. webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
  58. webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
  59. ])
  60. }
  61. override func viewDidLoad() {
  62. super.viewDidLoad()
  63. webView.navigationDelegate = self
  64. webView.uiDelegate = self
  65. // TODO(boyan01) Make it configuable from flutter.
  66. webView.configuration.preferences.javaEnabled = true
  67. webView.configuration.preferences.minimumFontSize = 12
  68. webView.configuration.preferences.javaScriptCanOpenWindowsAutomatically = true
  69. webView.configuration.allowsAirPlayForMediaPlayback = true
  70. webView.configuration.mediaTypesRequiringUserActionForPlayback = .video
  71. webView.addObserver(self, forKeyPath: "canGoBack", options: .new, context: nil)
  72. webView.addObserver(self, forKeyPath: "canGoForward", options: .new, context: nil)
  73. webView.addObserver(self, forKeyPath: "loading", options: .new, context: nil)
  74. defaultUserAgent = webView.value(forKey: "userAgent") as? String
  75. }
  76. override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
  77. if keyPath == "canGoBack" || keyPath == "canGoForward" {
  78. methodChannel.invokeMethod("onHistoryChanged", arguments: [
  79. "id": viewId,
  80. "canGoBack": webView.canGoBack,
  81. "canGoForward": webView.canGoForward,
  82. ] as [String: Any])
  83. } else if keyPath == "loading" {
  84. if webView.isLoading {
  85. methodChannel.invokeMethod("onNavigationStarted", arguments: [
  86. "id": viewId,
  87. ])
  88. } else {
  89. methodChannel.invokeMethod("onNavigationCompleted", arguments: [
  90. "id": viewId,
  91. ])
  92. }
  93. }
  94. }
  95. func load(url: URL) {
  96. debugPrint("load url: \(url)")
  97. webView.load(URLRequest(url: url))
  98. }
  99. func addJavascriptInterface(name: String) {
  100. javaScriptHandlerNames.append(name)
  101. webView.configuration.userContentController.add(self, name: name)
  102. }
  103. func removeJavascriptInterface(name: String) {
  104. if let index = javaScriptHandlerNames.firstIndex(of: name) {
  105. javaScriptHandlerNames.remove(at: index)
  106. }
  107. webView.configuration.userContentController.removeScriptMessageHandler(forName: name)
  108. }
  109. func addScriptToExecuteOnDocumentCreated(javaScript: String) {
  110. webView.configuration.userContentController.addUserScript(
  111. WKUserScript(source: javaScript, injectionTime: .atDocumentStart, forMainFrameOnly: true))
  112. }
  113. func setApplicationNameForUserAgent(applicationName: String) {
  114. webView.customUserAgent = (defaultUserAgent ?? "") + applicationName
  115. }
  116. func destroy() {
  117. webView.removeObserver(self, forKeyPath: "canGoBack")
  118. webView.removeObserver(self, forKeyPath: "canGoForward")
  119. webView.removeObserver(self, forKeyPath: "loading")
  120. webView.uiDelegate = nil
  121. webView.navigationDelegate = nil
  122. javaScriptHandlerNames.forEach { name in
  123. webView.configuration.userContentController.removeScriptMessageHandler(forName: name)
  124. }
  125. webView.configuration.userContentController.removeAllUserScripts()
  126. }
  127. func reload() {
  128. webView.reload()
  129. }
  130. func goBack() {
  131. if webView.canGoBack {
  132. webView.goBack()
  133. }
  134. }
  135. func goForward() {
  136. if webView.canGoForward {
  137. webView.goForward()
  138. }
  139. }
  140. func stopLoading() {
  141. webView.stopLoading()
  142. }
  143. func evaluateJavaScript(javaScriptString: String, completer: @escaping FlutterResult) {
  144. webView.evaluateJavaScript(javaScriptString) { result, error in
  145. if let error = error {
  146. completer(FlutterError(code: "1", message: error.localizedDescription, details: nil))
  147. return
  148. }
  149. completer(result)
  150. }
  151. }
  152. }
  153. extension WebViewLayoutController: WKNavigationDelegate {
  154. func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
  155. guard let url = navigationAction.request.url else {
  156. decisionHandler(.cancel)
  157. return
  158. }
  159. guard ["http", "https", "file"].contains(url.scheme?.lowercased() ?? "") else {
  160. decisionHandler(.cancel)
  161. return
  162. }
  163. methodChannel.invokeMethod("onUrlRequested", arguments: [
  164. "id": viewId,
  165. "url": url.absoluteString,
  166. ] as [String: Any])
  167. decisionHandler(.allow)
  168. }
  169. func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
  170. decisionHandler(.allow)
  171. }
  172. }
  173. extension WebViewLayoutController: WKUIDelegate {
  174. func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
  175. methodChannel.invokeMethod(
  176. "runJavaScriptTextInputPanelWithPrompt",
  177. arguments: [
  178. "id": viewId,
  179. "prompt": prompt,
  180. "defaultText": defaultText ?? "",
  181. ] as [String: Any]) { result in
  182. completionHandler((result as? String) ?? "")
  183. }
  184. }
  185. func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
  186. if !(navigationAction.targetFrame?.isMainFrame ?? false) {
  187. webView.load(navigationAction.request)
  188. }
  189. return nil
  190. }
  191. }
  192. extension WebViewLayoutController: WKScriptMessageHandler {
  193. func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
  194. methodChannel.invokeMethod(
  195. "onJavaScriptMessage",
  196. arguments: [
  197. "id": viewId,
  198. "name": message.name,
  199. "body": message.body,
  200. ] as [String: Any])
  201. }
  202. }