Search code examples
javascriptiostwitterwebviewwkwebview

iOS WKWebView improperly calculating height when there is embedded tweet


My app uses WKWebView to present html string. WkWebView is embedded in a scrollView because i have several other elements (e.g. buttons, tableViews). Because of that i need to calculate the size of WkWebvView content and for that i use evaluateJavaScript(“document.body.scrollHeight”, completionHandler: method. Whenever i have embedded tweet in a html string, content height is wrongly calculated and my webview cuts a piece of its height.

Part of html string which has tweet embedded looks like this:

<blockquote class=\"twitter-tweet\" data-lang=\"en\"><p><a href=\"https://twitter.com/hashtag/SYRIA?src=hash\">#SYRIA</a> Admiral Essen frigate launched <a href=\"https://twitter.com/hashtag/Kalibr?src=hash\">#Kalibr</a> cruise missiles against ISIS objects near Deir ez-Zor <a href=\"https://BLABLA*\">pic.twitter.com/azHAIii07g</a></p><p>— Минобороны России (@mod_russia) <a href=\"https://twitter.com/mod_russia/status/905007557554700288\">September 5, 2017</a></p></blockquote><p><script src=\"https://platform.twitter.com/widgets.js\"></script>

(BLABLA* is actually t.co/azHAIii07g which i had to replace because of stackoverflow rules)

Is there something missing in this twitter code, or should i set something in webview methods?

I tried implementing solution from this post: iOS Calculate correct WKWebview height for html string with embeded tweet but there was no success or at least i could figure out the answer.

Any advice is appreciated??

UPDATE: I hacked a little bit and found horrible workaround, but it is a good lead for where the problem lies. This is the full function which i use for calculating height:

`func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    self.webViewx.evaluateJavaScript("document.readyState", completionHandler: {
        [weak self] (complete, error) in
        if complete != nil {                self?.webViewx.evaluateJavaScript("document.body.offsetHeight", completionHandler: {
                [weak self] (height, error) in

    })
}`

If i put:

`DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+1, execute: { 
self?.webViewx.evaluateJavaScript("document.body.offsetHeight", completionHandler: {
[weak self] (height, error) in
print("HEIGHT AFTER 1 sec", height)
    })
})`

I get correct height after 1 second. Now, what would be the way to get that height update after correct time.

Also, one side note, if i have embedded facebook or instagram post, everything works as it should and webview height gets properly calculated.


Solution

  • With the help several posts on stackoverflow (especially: Twitter Uncaught TypeError: undefined is not a function), and my colleague who is a web developer, I managed to find a solution to this issue. Basically, the idea is to add a script to the webview configuration if there is an embedded tweet in a html string which webview needs to load. After that we should implement userContentController didReceive message function and in it we should evaluate the height of a webview.

    Here is the code which should go before webview.loadHtmlString method:

    if htmlString.contains("platform.twitter.com/widgets.js") {
            let twitterJS: String = "window.twttr = (function(d, s, id) {var js, fjs = d.getElementsByTagName(s)[0],t = window.twttr || {};if (d.getElementById(id)) return t;js = d.createElement(s);js.id = id;js.src = \"https://platform.twitter.com/widgets.js\";fjs.parentNode.insertBefore(js, fjs);t._e = [];t.ready = function(f) {t._e.push(f);};return t;}(document, \"script\", \"twitter-wjs\"));twttr.ready(function (twttr) {twttr.events.bind('loaded', function (event) {window.webkit.messageHandlers.callbackHandler.postMessage('TWEET');});});"
            let userScript = WKUserScript(source: twitterJS, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
            webViewx.configuration.userContentController.removeAllUserScripts()
            webViewx.configuration.userContentController.addUserScript(userScript)
            webViewx.configuration.userContentController.add(self, name: "callbackHandler")
        }
    

    First there is a check if a html string has a substring which is an embedded tweet. Script assigned to let twitterJS is something we got from twitter web page. Last line is important in a sense that it gives the name to our script.

    Actual height evaluation is done like this:

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "callbackHandler" {
            print(message.body)
            webViewx.evaluateJavaScript("document.body.offsetHeight", completionHandler: { (height, error) in
                if let error = error {
                    print(error.localizedDescription)
                    return
                }
                if let height = height as? CGFloat{
                    if height > self.kontejnerHeight.constant {
                        self.kontejnerHeight.constant = height
                    }
    
                }
            })
        }
    }
    

    Hopefully this will help the others as well.