diff --git a/README.md b/README.md new file mode 100644 index 0000000..f993d84 --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +# SwiftUIWebView(with messaging) +This project shows how to implement WebView in SwiftUI using WKWebView and UIViewRepresentable. It also shows how to deal with WebView in different purposes. + +**Make a WebView using WKWebView and UIViewRepresentable:** + +Just for ease of use first create a ViewModel, a enum class named _WebViewNavigation_ and another enum named _WebUrlType_. _WebViewNavigation_ is used to navigate WebView forward and backward. _WebUrlType_ defines what type of url should load e.g. `.localUrl` or `.publicUrl`. + +We can use any [UIKit](https://developer.apple.com/documentation/uikit) View into SwiftUI using UIViewRepresentable which is a view wrapper. Since we are going to make a WebView for SwiftUI, first we will create a structure named `WebView` that inherits `UIViewRepresentable` and implements necessary protocols. Then we will create a `Coordinator` inside `WebView` to implement necessary protocols of WKWebView. + +**Load a local** `**.html**` **file or a public website or web app in WebView:** + +In the above `WebView`, inside `Coordinator` class, function `updateUIView` loads the url of local or private website into WebView depending on `WebUrlType`. + + if let url = Bundle.main.url(forResource: “LocalWebsite”, withExtension: “html”, subdirectory: “www”) { + webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent()) + } + +Above lines load a website named `LocalWebsite.html` located inside our iOS project’s “www” directory. I mean it local webpage because it is inside the project file. + +These lines load a public website or web app + + if let url = URL(string: "https://www.example.com") { + webView.load(URLRequest(url: url)) + } + +Get the title of the loaded website + +In `ContentView` use `WebView` as follows + + WebView(url: .localUrl, viewModel: viewModel) + +**Interact between iOS native app and web app through JavaScript’s function** + +**Receive value from web app:** + +Inside `WebView`’s `makeUIView` function we passed a `iOSNative` as the name to the loaded web app so that web app can find our native iOS app through that name to pass value to our native iOS app. See below line: + + configuration.userContentController.add(self.makeCoordinator(), name: “iOSNative”) + +Make an extension to Coordinator to catch value sent from web app like: + +Here [WKScriptMessageHandler](https://developer.apple.com/documentation/webkit/wkscriptmessagehandler) receives all values sent from web app. Here is our web app that is sending those values. This `LocalWebsite.html` is located inside our project’s `www` directory. I created this `.html` file for test purpose, obviously you will work with server hosted web app. + +**Pass value to web app:** + +Since this is a static .html file it can not receive any value from iOS app but your real web app can receive. Just call a JavaScript function from your iOS as follows: + +In my case web app’s JavaScript function’s name is `valueGotFromIOS`. + +**Intercepting web app navigation:** + +To intercept every navigation of web app, just implement following function inside `Coordinator class`. + +**Navigating web app backward, forward and reloading:** + +For backward navigation use + + if webView.canGoBack { + webView.goBack() + } + +For forward navigation use + + if webView.canGoForward { + webView.goForward() + } + +To reload WebView use + + webView.reload() + +Finally build the app in [Xcode](https://developer.apple.com/xcode/)! + +Reference [Md Yamin](https://medium.com/@mdyamin/swiftui-mastering-webview-5790e686833e) diff --git a/SwiftUIWebView.xcodeproj/project.xcworkspace/xcuserdata/radekmezulanik.xcuserdatad/UserInterfaceState.xcuserstate b/SwiftUIWebView.xcodeproj/project.xcworkspace/xcuserdata/radekmezulanik.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..ad07df1 Binary files /dev/null and b/SwiftUIWebView.xcodeproj/project.xcworkspace/xcuserdata/radekmezulanik.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/SwiftUIWebView.xcodeproj/xcuserdata/radekmezulanik.xcuserdatad/xcschemes/xcschememanagement.plist b/SwiftUIWebView.xcodeproj/xcuserdata/radekmezulanik.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..41e0c3c --- /dev/null +++ b/SwiftUIWebView.xcodeproj/xcuserdata/radekmezulanik.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + SwiftUIWebView.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/SwiftUIWebView/ContentView.swift b/SwiftUIWebView/ContentView.swift index fdaca46..3caad80 100644 --- a/SwiftUIWebView/ContentView.swift +++ b/SwiftUIWebView/ContentView.swift @@ -13,6 +13,7 @@ struct ContentView: View { @State var showLoader = false @State var message = "" @State var webTitle = "" + @State var isLocal: Bool = true // For WebView's forward and backward navigation var webViewNavigationBar: some View { @@ -71,6 +72,8 @@ struct ContentView: View { at runtime where dynamic web app can */ HStack { + Toggle(isOn: $isLocal) {} + .labelsHidden() TextField("Write message", text: $message).textFieldStyle(RoundedBorderTextFieldStyle()) Button(action: { self.viewModel.valuePublisher.send(self.message) @@ -94,7 +97,7 @@ struct ContentView: View { /* This is our WebView. Here if you pass .localUrl it will load LocalWebsite.html file into the WebView and if you pass .publicUrl it will load the public website depending on your url provided. See WebView implementation for more info. */ - WebView(url: .publicUrl, viewModel: viewModel).overlay ( + WebView(url: isLocal ? .localUrl : .publicUrl, viewModel: viewModel).overlay ( RoundedRectangle(cornerRadius: 4, style: .circular) .stroke(Color.gray, lineWidth: 0.5) ).padding(.leading, 20).padding(.trailing, 20) diff --git a/SwiftUIWebView/WebView.swift b/SwiftUIWebView/WebView.swift index 8cd6ec8..18b9b1a 100644 --- a/SwiftUIWebView/WebView.swift +++ b/SwiftUIWebView/WebView.swift @@ -103,10 +103,11 @@ struct WebView: UIViewRepresentable, WebViewHandlerDelegate { /* 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));" + let javascriptFunction = "valueGotFromIOS('\(value)')" webView.evaluateJavaScript(javascriptFunction) { (response, error) in if let error = error { print("Error calling javascript:valueGotFromIOS()") + print(error) print(error.localizedDescription) } else { print("Called javascript:valueGotFromIOS()") diff --git a/SwiftUIWebView/www/LocalWebsite.html b/SwiftUIWebView/www/LocalWebsite.html index 4e07e38..0e8dcae 100644 --- a/SwiftUIWebView/www/LocalWebsite.html +++ b/SwiftUIWebView/www/LocalWebsite.html @@ -41,8 +41,9 @@

SwiftUI JavaScript Interaction

window.webkit.messageHandlers.iOSNative.postMessage("Bla Bla Bla"); } - function valueGotFromIOS(value) { + function valueGotFromIOS(value = "Default response if nothing received") { document.getElementById("output").innerHTML = value; + window.webkit.messageHandlers.iOSNative.postMessage(value); }