YXWebView.swift 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. //
  2. // YXWebView.swift
  3. // fiveConstant
  4. //
  5. // Created by 李建 on 2023/1/14.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SwiftUI
  10. import Combine
  11. import WebKit
  12. // MARK: - WebViewHandlerDelegate
  13. // For printing values received from web app
  14. protocol WebViewHandlerDelegate {
  15. func receivedJsonValueFromWebView(value: [String: Any?])
  16. func receivedStringValueFromWebView(value: String)
  17. }
  18. // MARK: - WebView
  19. struct WebView: UIViewRepresentable, WebViewHandlerDelegate {
  20. func receivedJsonValueFromWebView(value: [String : Any?]) {
  21. print("JSON value received from web is: \(value)")
  22. }
  23. func receivedStringValueFromWebView(value: String) {
  24. print("String value received from web is: \(value)")
  25. }
  26. var url: WebUrlType
  27. // Viewmodel object
  28. @ObservedObject var viewModel: ViewModel
  29. // Make a coordinator to co-ordinate with WKWebView's default delegate functions
  30. func makeCoordinator() -> Coordinator {
  31. Coordinator(self)
  32. }
  33. func makeUIView(context: Context) -> WKWebView {
  34. // Enable javascript in WKWebView
  35. let preferences = WKPreferences()
  36. let configuration = WKWebViewConfiguration()
  37. // Here "iOSNative" is our delegate name that we pushed to the website that is being loaded
  38. configuration.userContentController.add(self.makeCoordinator(), name: "iOSNative")
  39. configuration.preferences = preferences
  40. let webView = WKWebView(frame: CGRect.zero, configuration: configuration)
  41. webView.navigationDelegate = context.coordinator
  42. webView.allowsBackForwardNavigationGestures = true
  43. webView.scrollView.isScrollEnabled = true
  44. return webView
  45. }
  46. func updateUIView(_ webView: WKWebView, context: Context) {
  47. if url == .localUrl {
  48. // Load local website
  49. if let url = Bundle.main.url(forResource: "LocalWebsite", withExtension: "html", subdirectory: "www") {
  50. webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
  51. }
  52. } else if url == .publicUrl {
  53. // Load a public website, for example I used here google.com
  54. if let url = URL(string: "https://www.google.com") {
  55. webView.load(URLRequest(url: url))
  56. }
  57. }
  58. }
  59. class Coordinator : NSObject, WKNavigationDelegate {
  60. var parent: WebView
  61. var delegate: WebViewHandlerDelegate?
  62. var valueSubscriber: AnyCancellable? = nil
  63. var webViewNavigationSubscriber: AnyCancellable? = nil
  64. init(_ uiWebView: WebView) {
  65. self.parent = uiWebView
  66. self.delegate = parent
  67. }
  68. deinit {
  69. valueSubscriber?.cancel()
  70. webViewNavigationSubscriber?.cancel()
  71. }
  72. func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
  73. // Get the title of loaded webcontent
  74. webView.evaluateJavaScript("document.title") { (response, error) in
  75. if let error = error {
  76. print("Error getting title")
  77. print(error.localizedDescription)
  78. }
  79. guard let title = response as? String else {
  80. return
  81. }
  82. self.parent.viewModel.showWebTitle.send(title)
  83. }
  84. /* An observer that observes 'viewModel.valuePublisher' to get value from TextField and
  85. pass that value to web app by calling JavaScript function */
  86. valueSubscriber = parent.viewModel.valuePublisher.receive(on: RunLoop.main).sink(receiveValue: { value in
  87. let javascriptFunction = "valueGotFromIOS(\(value));"
  88. webView.evaluateJavaScript(javascriptFunction) { (response, error) in
  89. if let error = error {
  90. print("Error calling javascript:valueGotFromIOS()")
  91. print(error.localizedDescription)
  92. } else {
  93. print("Called javascript:valueGotFromIOS()")
  94. }
  95. }
  96. })
  97. // Page loaded so no need to show loader anymore
  98. self.parent.viewModel.showLoader.send(false)
  99. }
  100. /* Here I implemented most of the WKWebView's delegate functions so that you can know them and
  101. can use them in different necessary purposes */
  102. func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
  103. // Hides loader
  104. parent.viewModel.showLoader.send(false)
  105. }
  106. func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
  107. // Hides loader
  108. parent.viewModel.showLoader.send(false)
  109. }
  110. func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
  111. // Shows loader
  112. parent.viewModel.showLoader.send(true)
  113. }
  114. func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
  115. // Shows loader
  116. parent.viewModel.showLoader.send(true)
  117. self.webViewNavigationSubscriber = self.parent.viewModel.webViewNavigationPublisher.receive(on: RunLoop.main).sink(receiveValue: { navigation in
  118. switch navigation {
  119. case .backward:
  120. if webView.canGoBack {
  121. webView.goBack()
  122. }
  123. case .forward:
  124. if webView.canGoForward {
  125. webView.goForward()
  126. }
  127. case .reload:
  128. webView.reload()
  129. }
  130. })
  131. }
  132. // This function is essential for intercepting every navigation in the webview
  133. func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
  134. // Suppose you don't want your user to go a restricted site
  135. // Here you can get many information about new url from 'navigationAction.request.description'
  136. if let host = navigationAction.request.url?.host {
  137. if host == "restricted.com" {
  138. // This cancels the navigation
  139. decisionHandler(.cancel)
  140. return
  141. }
  142. }
  143. // This allows the navigation
  144. decisionHandler(.allow)
  145. }
  146. }
  147. }
  148. // MARK: - Extensions
  149. extension WebView.Coordinator: WKScriptMessageHandler {
  150. func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
  151. // Make sure that your passed delegate is called
  152. if message.name == "iOSNative" {
  153. if let body = message.body as? [String: Any?] {
  154. delegate?.receivedJsonValueFromWebView(value: body)
  155. } else if let body = message.body as? String {
  156. delegate?.receivedStringValueFromWebView(value: body)
  157. }
  158. }
  159. }
  160. }