123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- //
- // WebView.swift
- // SwiftUIWebView
- //
- // Created by Md. Yamin on 4/25/20.
- // Copyright © 2020 Md. Yamin. All rights reserved.
- //
- import Foundation
- import UIKit
- import SwiftUI
- import Combine
- import WebKit
- // MARK: - WebViewHandlerDelegate
- // For printing values received from web app
- protocol WebViewHandlerDelegate {
- func receivedJsonValueFromWebView(value: [String: Any?])
- func receivedStringValueFromWebView(value: String)
- }
- // MARK: - WebView
- struct WebView: UIViewRepresentable, WebViewHandlerDelegate {
- func receivedJsonValueFromWebView(value: [String : Any?]) {
- print("JSON value received from web is: \(value)")
- }
-
- func receivedStringValueFromWebView(value: String) {
- print("String value received from web is: \(value)")
- }
-
- var url: WebUrlType
-
- var http_url : String = ""
-
- // Viewmodel object
- @ObservedObject var viewModel: ViewModel
-
- // Make a coordinator to co-ordinate with WKWebView's default delegate functions
- func makeCoordinator() -> Coordinator {
- Coordinator(self)
- }
-
- func makeUIView(context: Context) -> WKWebView {
- // Enable javascript in WKWebView
- let preferences = WKPreferences()
- preferences.javaScriptEnabled = true
-
- let configuration = WKWebViewConfiguration()
- // Here "iOSNative" is our delegate name that we pushed to the website that is being loaded
- configuration.userContentController.add(self.makeCoordinator(), name: "iOSNative")
- configuration.preferences = preferences
-
- let webView = WKWebView(frame: CGRect.zero, configuration: configuration)
- webView.navigationDelegate = context.coordinator
- webView.allowsBackForwardNavigationGestures = true
- webView.scrollView.isScrollEnabled = true
- return webView
- }
-
- func updateUIView(_ webView: WKWebView, context: Context) {
- if url == .localUrl {
- // Load local website
- if let url = Bundle.main.url(forResource: "LocalWebsite", withExtension: "html", subdirectory: "www") {
- webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
- }
- } else if url == .publicUrl {
- // Load a public website, for example I used here google.com
- if let url = URL(string: http_url) {
- webView.load(URLRequest(url: url))
- }
- }
- }
-
- class Coordinator : NSObject, WKNavigationDelegate {
- var parent: WebView
- var delegate: WebViewHandlerDelegate?
- var valueSubscriber: AnyCancellable? = nil
- var webViewNavigationSubscriber: AnyCancellable? = nil
-
- init(_ uiWebView: WebView) {
- self.parent = uiWebView
- self.delegate = parent
- }
-
- deinit {
- valueSubscriber?.cancel()
- webViewNavigationSubscriber?.cancel()
- }
-
- func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
- // Get the title of loaded webcontent
- webView.evaluateJavaScript("document.title") { (response, error) in
- if let error = error {
- print("Error getting title")
- print(error.localizedDescription)
- }
-
- guard let title = response as? String else {
- return
- }
-
- self.parent.viewModel.showWebTitle.send(title)
- }
-
- /* An observer that observes 'viewModel.valuePublisher' to get value from TextField and
- pass that value to web app by calling JavaScript function */
- valueSubscriber = parent.viewModel.valuePublisher.receive(on: RunLoop.main).sink(receiveValue: { value in
- let javascriptFunction = "valueGotFromIOS(\(value));"
- webView.evaluateJavaScript(javascriptFunction) { (response, error) in
- if let error = error {
- print("Error calling javascript:valueGotFromIOS()")
- print(error.localizedDescription)
- } else {
- print("Called javascript:valueGotFromIOS()")
- }
- }
- })
-
- // Page loaded so no need to show loader anymore
- self.parent.viewModel.showLoader.send(false)
- }
-
- /* Here I implemented most of the WKWebView's delegate functions so that you can know them and
- can use them in different necessary purposes */
-
- func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
- // Hides loader
- parent.viewModel.showLoader.send(false)
- }
-
- func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
- // Hides loader
- parent.viewModel.showLoader.send(false)
- }
-
- func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
- // Shows loader
- parent.viewModel.showLoader.send(true)
- }
-
- func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
- // Shows loader
- parent.viewModel.showLoader.send(true)
- self.webViewNavigationSubscriber = self.parent.viewModel.webViewNavigationPublisher.receive(on: RunLoop.main).sink(receiveValue: { navigation in
- switch navigation {
- case .backward:
- if webView.canGoBack {
- webView.goBack()
- }
- case .forward:
- if webView.canGoForward {
- webView.goForward()
- }
- case .reload:
- webView.reload()
- }
- })
- }
-
- // This function is essential for intercepting every navigation in the webview
- func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
- // Suppose you don't want your user to go a restricted site
- // Here you can get many information about new url from 'navigationAction.request.description'
- if let host = navigationAction.request.url?.host {
- if host == "restricted.com" {
- // This cancels the navigation
- decisionHandler(.cancel)
- return
- }
- }
-
- guard let curUrl = navigationAction.request.url else {
- decisionHandler(.allow); return
- }
-
- if curUrl.absoluteString.hasPrefix("alipay://alipayclient/") || curUrl.absoluteString.hasPrefix("weixin://"){
- decisionHandler(WKNavigationActionPolicy.cancel)
- if let url = URL (string: curUrl.absoluteString) {
- //根据iOS系统版本,分别处理
- if #available(iOS 10, *) {
- UIApplication .shared.open(url, options: [:],
- completionHandler: {
- (success) in
- })
- } else {
- UIApplication .shared.openURL(url)
- }
- }
- return
- }
- // This allows the navigation
- decisionHandler(.allow)
- }
- }
- }
- // MARK: - Extensions
- extension WebView.Coordinator: WKScriptMessageHandler {
- func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
- // Make sure that your passed delegate is called
- if message.name == "iOSNative" {
- if let body = message.body as? [String: Any?] {
- delegate?.receivedJsonValueFromWebView(value: body)
- } else if let body = message.body as? String {
- delegate?.receivedStringValueFromWebView(value: body)
- }
- }
- }
- }
|