Skip to content

Instantly share code, notes, and snippets.

@prafullakumar
Last active March 1, 2023 14:41
Show Gist options
  • Save prafullakumar/937c5d908e44f0497862c2beb835d7b3 to your computer and use it in GitHub Desktop.
Save prafullakumar/937c5d908e44f0497862c2beb835d7b3 to your computer and use it in GitHub Desktop.
import SwiftUI
import WebKit
struct ContentView: View {
@ObservedObject var webViewStateModel: WebViewStateModel = WebViewStateModel()
var body: some View {
NavigationView {
LoadingView(isShowing: .constant(webViewStateModel.loading)) { //loading logic taken from https://stackoverflow.com/a/56496896/9838937
//Add onNavigationAction if callback needed
WebView(url: URL.init(string: "https://www.google.com")!, webViewStateModel: self.webViewStateModel)
}
.navigationBarTitle(Text(webViewStateModel.pageTitle), displayMode: .inline)
.navigationBarItems(trailing:
Button("Last Page") {
self.webViewStateModel.goBack.toggle()
}.disabled(!webViewStateModel.canGoBack)
)
}
}
}
struct ActivityIndicator: UIViewRepresentable {
@Binding var isAnimating: Bool
let style: UIActivityIndicatorView.Style
func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> UIActivityIndicatorView {
return UIActivityIndicatorView(style: style)
}
func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicator>) {
isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
}
}
struct LoadingView<Content>: View where Content: View {
@Binding var isShowing: Bool
var content: () -> Content
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .center) {
self.content()
.disabled(self.isShowing)
.blur(radius: self.isShowing ? 3 : 0)
VStack {
Text("Loading...")
ActivityIndicator(isAnimating: .constant(true), style: .large)
}
.frame(width: geometry.size.width / 2,
height: geometry.size.height / 5)
.background(Color.secondary.colorInvert())
.foregroundColor(Color.primary)
.cornerRadius(20)
.opacity(self.isShowing ? 1 : 0)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
///// Implementaton
class WebViewStateModel: ObservableObject {
@Published var pageTitle: String = "Web View"
@Published var loading: Bool = false
@Published var canGoBack: Bool = false
@Published var goBack: Bool = false
}
struct WebView: View {
enum NavigationAction {
case decidePolicy(WKNavigationAction, (WKNavigationActionPolicy) -> Void) //mendetory
case didRecieveAuthChallange(URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) //mendetory
case didStartProvisionalNavigation(WKNavigation)
case didReceiveServerRedirectForProvisionalNavigation(WKNavigation)
case didCommit(WKNavigation)
case didFinish(WKNavigation)
case didFailProvisionalNavigation(WKNavigation,Error)
case didFail(WKNavigation,Error)
}
@ObservedObject var webViewStateModel: WebViewStateModel
private var actionDelegate: ((_ navigationAction: WebView.NavigationAction) -> Void)?
let uRLRequest: URLRequest
var body: some View {
WebViewWrapper(webViewStateModel: webViewStateModel,
action: actionDelegate,
request: uRLRequest)
}
/*
if passed onNavigationAction it is mendetory to complete URLAuthenticationChallenge and decidePolicyFor callbacks
*/
init(uRLRequest: URLRequest, webViewStateModel: WebViewStateModel, onNavigationAction: ((_ navigationAction: WebView.NavigationAction) -> Void)?) {
self.uRLRequest = uRLRequest
self.webViewStateModel = webViewStateModel
self.actionDelegate = onNavigationAction
}
init(url: URL, webViewStateModel: WebViewStateModel, onNavigationAction: ((_ navigationAction: WebView.NavigationAction) -> Void)? = nil) {
self.init(uRLRequest: URLRequest(url: url),
webViewStateModel: webViewStateModel,
onNavigationAction: onNavigationAction)
}
}
/*
A weird case: if you change WebViewWrapper to struct cahnge in WebViewStateModel will never call updateUIView
*/
final class WebViewWrapper : UIViewRepresentable {
@ObservedObject var webViewStateModel: WebViewStateModel
let action: ((_ navigationAction: WebView.NavigationAction) -> Void)?
let request: URLRequest
init(webViewStateModel: WebViewStateModel,
action: ((_ navigationAction: WebView.NavigationAction) -> Void)?,
request: URLRequest) {
self.action = action
self.request = request
self.webViewStateModel = webViewStateModel
}
func makeUIView(context: Context) -> WKWebView {
let view = WKWebView()
view.navigationDelegate = context.coordinator
view.load(request)
return view
}
func updateUIView(_ uiView: WKWebView, context: Context) {
if uiView.canGoBack, webViewStateModel.goBack {
uiView.goBack()
webViewStateModel.goBack = false
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(action: action, webViewStateModel: webViewStateModel)
}
final class Coordinator: NSObject {
@ObservedObject var webViewStateModel: WebViewStateModel
let action: ((_ navigationAction: WebView.NavigationAction) -> Void)?
init(action: ((_ navigationAction: WebView.NavigationAction) -> Void)?,
webViewStateModel: WebViewStateModel) {
self.action = action
self.webViewStateModel = webViewStateModel
}
}
}
extension WebViewWrapper.Coordinator: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if action == nil {
decisionHandler(.allow)
} else {
action?(.decidePolicy(navigationAction, decisionHandler))
}
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
webViewStateModel.loading = true
action?(.didStartProvisionalNavigation(navigation))
}
func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
action?(.didReceiveServerRedirectForProvisionalNavigation(navigation))
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
webViewStateModel.loading = false
webViewStateModel.canGoBack = webView.canGoBack
action?(.didFailProvisionalNavigation(navigation, error))
}
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
action?(.didCommit(navigation))
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webViewStateModel.loading = false
webViewStateModel.canGoBack = webView.canGoBack
if let title = webView.title {
webViewStateModel.pageTitle = title
}
action?(.didFinish(navigation))
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
webViewStateModel.loading = false
webViewStateModel.canGoBack = webView.canGoBack
action?(.didFail(navigation, error))
}
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if action == nil {
completionHandler(.performDefaultHandling, nil)
} else {
action?(.didRecieveAuthChallange(challenge, completionHandler))
}
}
}
@saldous
Copy link

saldous commented Nov 28, 2022

This no longer works in iOS 16. Fatal error: UIViewRepresentables must be value types: WebViewWrapper.

final class WebViewWrapper : UIViewRepresentable needs to be a Struct I think?

@mattan8844
Copy link

you should update the final class to struct, class is not a value type

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment