swiftuiwkwebviewuiactivityindicatorview

SwiftUI How can I add an activity indicator in WKWebView?


How can I add an activity indicator in WKWebView which will display the indicator while the webpage is loading and disappears when loaded ?

I've looked at some of the old posts but could not figure out how to do it in SwiftUI see link to one of the old solutions below How to add Activity Indicator to WKWebView (Swift 3)


Solution

  • Use UIViewRepresentable to create a UIActivityIndicatorView:

    You control when an activity indicator animates by calling the startAnimating() and stopAnimating() methods. To automatically hide the activity indicator when animation stops, set the hidesWhenStopped property to true.

    You can set the color of the activity indicator by using the color property.

    struct ActivityIndicatorView: UIViewRepresentable {
        @Binding var isAnimating: Bool
        let style: UIActivityIndicatorView.Style
        
        func makeUIView(context: UIViewRepresentableContext<ActivityIndicatorView>) -> UIActivityIndicatorView {
            return UIActivityIndicatorView(style: style)
        }
        
        func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicatorView>) {
            isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
        }
    }
    

    Create a LoadingView to allow you to wrap around your views:

    This allows you to style the activity views content.

    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...")
                        ActivityIndicatorView(isAnimating: .constant(true), style: .large)
                    }
                    .frame(width: geometry.size.width / 2, height: geometry.size.height / 5)
                    .background(Color.secondary.colorInvert())
                    .foregroundColor(Color.red)
                    .cornerRadius(20)
                    .opacity(self.isShowing ? 1 : 0)
                    
                }
            }
        }
    }
    

    If you want to be able to update the LoadingView(...) status you'll need to introduce a view model that inherits from ObservableObject:

    Based on this answer: https://stackoverflow.com/a/58825642/264802

    class WebViewModel: ObservableObject {
        @Published var url: String
        @Published var isLoading: Bool = true
        
        init (url: String) {
            self.url = url
        }
    }
    
    struct WebView: UIViewRepresentable {
        @ObservedObject var viewModel: WebViewModel
        let webView = WKWebView()
    
        func makeCoordinator() -> Coordinator {
            Coordinator(self.viewModel)
        }
        
        class Coordinator: NSObject, WKNavigationDelegate {
            private var viewModel: WebViewModel
            
            init(_ viewModel: WebViewModel) {
                self.viewModel = viewModel
            }
    
            func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
                self.viewModel.isLoading = false
            }
        }
        
        func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<WebView>) { }
        
        func makeUIView(context: Context) -> UIView {
            self.webView.navigationDelegate = context.coordinator
    
            if let url = URL(string: self.viewModel.url) {
                self.webView.load(URLRequest(url: url))
            }
    
            return self.webView
        }
    }
    

    Then to use it inside your views you would do the following:

    struct ContentView: View {
        @StateObject var model = WebViewModel(url: "http://www.google.com")
    
        var body: some View {
            LoadingView(isShowing: self.$model.isLoading) {
                WebView(viewModel: self.model)
            }
        }
    }