WebView.swift 8.2 KB

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