How Can I Fix WKWebView Smaller Content Height Value Problem?

I am loading Html content in my WebView.

When I load Html content inside WebView, it shows correctly. After that when I refresh WebView with new Html content the problem occurs.

For example: my first Html content's height is 900, it shows correctly and then I reload WebView with my new Html content which has a height is 400, the WebView is not shrinking and fitting to the content with new height. It still remains as long as 900 height.

So, it seems that WebView is able to re-size itself from less content which is previously loaded to more content but unable to re-size itself when the content is less than previously loaded content.

WebView

struct WebView: UIViewRepresentable {
    @State var htmlString: String
    @Binding var dynamicHeight: CGFloat

    func makeUIView(context: Context) -> WKWebView {
        let webView: WKWebView = WKWebView()
        webView.navigationDelegate = context.coordinator
        webView.scrollView.bounces = false
        webView.backgroundColor = .clear
        return webView
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        uiView.loadHTMLString(htmlString, baseURL: nil)
    }

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

    class Coordinator: NSObject, WKNavigationDelegate {
        var parent: WebView

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

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in
                if complete != nil {
                   webView.evaluateJavaScript("document.documentElement.scrollHeight", completionHandler: { (result, error) in
                            if let height = result as? CGFloat {
                                self.parent.dynamicHeight = height
                            }
                    })
                }
            })
        }
    }
}

Any Content which is I used WebView

struct WebTest: View {

    @ObservedObject var viewModel = NewsVM()
    @State var index: Int = 0
    @State var newID: String = ""
    let screen = UIScreen.main.bounds
    let formatString = "<html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0\"><script type=\"text/javascript\" src=\"https://platform.twitter.com/widgets.js\"></script><style>@media (prefers-color-scheme: dark) { body { background-color: #1c1c1e; color: white; }} img,iframe,blockquote {\n\t\t\tmax-width: \(UIScreen.main.bounds.width - 24);\n\t\t\theight: auto\n\t\t}\n\t\t</style></head><body><span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size:17\">%@</span></body></html>"
    @State private var webViewHeight: CGFloat = .zero

    init(id:String) {
        viewModel.fetchNews(id: id)
        self.newID = id
    }

    var body: some View {
        Group{
            if self.viewModel.news.count > 0 {
                        ModelPages(self.viewModel.news, currentPage: self.$index, wrap: false, hasControl: false) { pageIndex, new in
                        ScrollView(.vertical, showsIndicators: false){
                            Text("Height: \(self.webViewHeight)").padding()
                            WebView(htmlString: String(format: self.formatString, new.content), dynamicHeight: self.$webViewHeight)
                                    .frame(width: self.screen.width - 16)
                                    .frame(height: self.webViewHeight)
                                    .tag(pageIndex)
                                    .padding(.horizontal, 8)
                            }
                        }
            }else{
                Group{
                    if self.viewModel.error == true{
                        ConnectionError()
                    }else{
                        LoadingView()
                    }
                }
            }
        }
    }
}

Upvotes: 3

Views: 2174

Answers (3)

Emre Değirmenci
Emre Değirmenci

Reputation: 747

I had a similar problem within collectionView included accordion, expandable web data in WKWebView. It was not scrollable to the bottom due to the last expandable web item and solved this problem using the following solution:

class WebViewCollectionViewCell: WKNavigationDelegate {
    @IBOutlet private var webView: WKWebView!
    
    private var cancellable: AnyCancellable?
    var onWebViewFinished: ((CGFloat) -> Void)?
    
    func webView(_: WKWebView, didFinish _: WKNavigation!) {
        webView.contentHeight { [weak self] height in
            self?.onWebViewFinished?(height)
            UIView.animate(withDuration: 0.4) {
                self?.webView.alpha = 1 // Fix white flashing
            }
        }
        cancellable = webView?
            .publisher(for: \.scrollView.contentSize)
            .removeDuplicates()
            .sink { [weak self] size in
                self?.onWebViewFinished?(size.height)
            }
    }
}

extension WKWebView {
    func contentHeight(completion: @escaping ((CGFloat) -> Void)) {
        evaluateJavaScript("document.readyState") { [weak self] complete, _ in
            if complete.exists {
                self?.evaluateJavaScript("document.body.scrollHeight", completionHandler: { height, _ in
                    completion(height as? CGFloat ?? 0)
                })
            }
        }
    }
}

And the usage with UICollectionViewCell closure:

class ViewController: 
 UIViewController, 
 UICollectionViewDelegate,
 UICollectionViewDataSource,
 UICollectionViewDelegateFlowLayout {

private var webViewHeight: CGFloat = 1_000
private lazy var configurator: CollectionViewCell = {
  cell.onWebViewFinished = { height in
                self?.webViewHeight = height
                self?.onWebViewFinished?()
            }
            return cell
    }

Upvotes: 2

Jaekov Segovia
Jaekov Segovia

Reputation: 123

I solved this problem starting with this solution: https://stackoverflow.com/a/41511422/5965343

  1. Monitoring the onresize event, but with the following additions.

  2. Add/inject an <span id="endElement"></span> at ondocument ready event, end of body.

  3. Get the span-endElement Y in order to get the "true" height.
  4. put the resulting height on the webView.frame.Height

functions to inject with AddUserScript() onloadFunction

window.onload=function () {window.webkit.messageHandlers.sizeNotification.postMessage({justLoaded:true,height: document.body.scrollHeight,endElement:document.getElementById('endOfDocument').offsetTop});};

onresize

document.body.onresize=function() {window.webkit.messageHandlers.sizeNotification.postMessage({justLoaded:false,height: document.body.scrollHeight,endElement:document.getElementById('endOfDocument').offsetTop});};

INJECT span at the end

document.body.insertAdjacentHTML('beforeend',"<span style='height: 0;' id='endOfDocument'></span>" );

Upvotes: 1

Latheeshwar Raj
Latheeshwar Raj

Reputation: 11

Could you try wrapping the Webview within a parent UIView and load the page. Don't put a fixed height constraint for the UIView instead make the Webview define the height of the parent UIView. It might work.

Upvotes: 0

Related Questions