How to create a List view in SwiftUI with each List item being a WKWebView (my implementation is super slow and has problems)

Viewed 263

Basically, I want to create a List view in SwiftUI with each list item being a WKWebView (this is like the posts of a topic in a forum, each post data is represented by some HTML string, may include pictures and other stuff) Something like below something like this

However, when I complete the code (code attached at the bottom), the code execution is super slow, and at the same time, it is impossible to scroll. If you scroll down, some list items are not loaded. And if you scroll up again, the original loaded list items disappear. Also, there are a lot of error messages in the debug window, something like error

2020-08-08 22:44:15.493387-0700 WKWebViewTest[2205:76254] WF: === Starting WebFilter logging for process WKWebViewTest
2020-08-08 22:44:15.493682-0700 WKWebViewTest[2205:76254] WF: _userSettingsForUser : (null)
2020-08-08 22:44:15.493865-0700 WKWebViewTest[2205:76254] WF: _WebFilterIsActive returning: NO
2020-08-08 22:44:15.622585-0700 WKWebViewTest[2205:76254] WF: _userSettingsForUser : (null)
2020-08-08 22:44:15.622787-0700 WKWebViewTest[2205:76254] WF: _WebFilterIsActive returning: NO

I wonder how can I realize this view nicely and fast in loading speed in SwiftUI.

import SwiftUI
import WebKit

struct ContentView: View {
    init() {
        UITableView.appearance().separatorStyle = .none
        UITableViewCell.appearance().selectionStyle = .none
        UITableViewCell.appearance().backgroundColor = UIColor.clear
        UITableView.appearance().backgroundColor = UIColor.clear
    }

    let htmlText = "<p>This is the paragraph</p>"
    
    var body: some View {
        List{
            ForEach(0...20, id: \.self) { _ in
                VStack {
                    PostWebView(text: self.htmlText)
                        .frame(height: 50)
                    Divider()
                }
            }
        }
        .listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct PostWebView : UIViewRepresentable {
    let text: String
    
    init(text: String) {
        self.text = text
    }

    class Coordinator: NSObject, WKNavigationDelegate {
        var parent: PostWebView

        init(_ parent: PostWebView) {
            self.parent = parent
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> WKWebView  {
        let webview = WKWebView()
        webview.scrollView.bounces = false
        webview.scrollView.isScrollEnabled = false
        webview.navigationDelegate = context.coordinator
        let htmlStart = "<HTML><HEAD><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, shrink-to-fit=no, maximum-scale=1.0, user-scalable=no\"></HEAD><BODY>"
        let htmlEnd = "</BODY></HTML>"
        let postHtml = text
        let htmlString = "\(htmlStart)\(postHtml)\(htmlEnd)"
        webview.loadHTMLString(htmlString, baseURL:  nil)
        return webview
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
    }
}
1 Answers

I had the same requirement and resorted to not using a List but using a VStack in a ScrollView, and at the same time limiting the number of loaded items per page (20 in my case). The VStack will render all items once, without reloading when you scroll down and up. Therefor a LazyVStack doesn't work either, because it also reloads its contents when scrolling.

This also works well with auto-resizing the WebView to its loaded content. For visual optimization I make sure the first item(s) are loaded before the other items, for some strange reason SwiftUI seems to load the webview content in reverse order, starting with the latest in the list.

Related