Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Position of HTML element inside WKWebView

Given a WKWebView showing a web page, how can I get the position (into the frame of my WKWebView) of an HTML element identified by a specific css selector?

Example

Here the position of the highlighted DIV should be (0, 123)

enter image description here

And if a scroll the HTML content the position of the same DIV should be (0, 0)

enter image description here

Thanks

like image 400
Luca Angeletti Avatar asked Aug 02 '16 12:08

Luca Angeletti


1 Answers

The problem here is that WKWebView scales the the size of its content. The trick to finding the frame of an element in the scrollView is to find out how much the WKWebView is scaling the content by. You can find this scale by comparing the scroll view's content size with what javascript returns as the document size. Below is XCode playground content that I was able to run in XCode 8.3.2.

This will give you the position of images in the scroll view. To get the position in the web view, just subtract scrollview.contentOffset.y

TL;DR:

webView.evaluateJavaScript("document.documentElement.clientHeight;") { (value, error) in
    self.documentHeight = value as! CGFloat
    let zoom = webView.scollView.zoomScale
}

_

import Foundation
import WebKit
import XCPlayground

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)

let content = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam mauris massa, ornare et ullamcorper a, tristique et odio. Nullam eleifend ex id ante placerat, sit amet feugiat arcu mollis. Sed iaculis lobortis lacus, in fermentum urna sodales at. Integer eget eleifend risus. Aenean feugiat turpis in odio sodales, id volutpat nisi pellentesque. Nunc eleifend nisl nunc, quis blandit nulla posuere ultricies. Nullam varius accumsan neque, vitae imperdiet ante consectetur vel. Fusce ullamcorper eu odio eleifend egestas. <img src=\"https://imgs.xkcd.com/comics/wisdom_of_the_ancients.png\" width=\"98%\"/> Proin elementum odio non massa pulvinar laoreet. Duis tincidunt augue vel placerat euismod. Mauris tincidunt felis at tellus convallis bibendum. Suspendisse purus dolor, lacinia eget tempor feugiat, vestibulum in risus. In non dui nec nisl porttitor pulvinar. Aliquam consectetur nisl at arcu consequat dapibus. Sed ex ante, condimentum vitae diam non, cursus dapibus ligula.<br /><img src=\"https://imgs.xkcd.com/comics/wisdom_of_the_ancients.png\" width=\"98%\"/></p>"

let jsImages = "var images = document.getElementsByTagName('img');" +
    "var values = [];" +
    "for (var i = 0; i < images.length; i++) {" +
        "var rect = images[i].getBoundingClientRect();" +
        "values = values.concat(rect.left, rect.top, rect.width, rect.height);" +
    "};" +
    "values;"

let jsHeight = "document.documentElement.clientHeight;"

class CustomWebView: WKWebView, WKNavigationDelegate {

    var imageLocations: [CGRect] = []
    var documentHeight: CGFloat?

    func getImages()  {
        self.evaluateJavaScript(jsImages) { (values, error) in
            print("=====\nValues from Javascript: \(values)")
            if let values = values as? [Any] {
                let numImages = values.count / 4 // jsImages returns four-value sets
                for set in 0..<numImages {
                    let offset = set * 4
                    let imageFrame = self.jsToSwift(x: values[offset] as! CGFloat, y: values[offset+1] as! CGFloat, w: values[offset+2] as! CGFloat, h: values[offset+3] as! CGFloat)
                    self.imageLocations.append(imageFrame)
                }
                print("=====\nScrollview content size: \(self.scrollView.contentSize)")
                print("=====\nFrames in WebView's ScrollView:\n\(self.imageLocations)\n=====")
            }
        }
    }

    func getHeight(callback: @escaping (() -> Void)) {
        // Evaluate document height
        self.evaluateJavaScript(jsHeight) { (value, error) in
            self.documentHeight = value as? CGFloat
            callback()
        }
    }

    func jsToSwift(x: CGFloat, y: CGFloat, w: CGFloat, h: CGFloat) -> CGRect {
        guard let documentHeight = documentHeight else {
            return CGRect.zero
        }
        let zoom = scollView.zoomScale
        let swiftX = x * zoom
        let swiftY = y * zoom
        let swiftW = w * zoom
        let swiftH = h * zoom
        return CGRect(x: swiftX, y: swiftY, width: swiftW, height: swiftH)
    }

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        self.getHeight { [weak self] (error) in
            self?.getImages()
        }
    }

}

let webView = CustomWebView(frame: CGRect.zero, configuration: WKWebViewConfiguration())
webView.navigationDelegate = webView
webView.loadHTMLString(content, baseURL: nil)

Example output:

=====
Values from Javascript: Optional(<__NSArrayM 0x610000049510>(
8,
1528,
485,
270,
8,
2878,
485,
270
)
)
=====
Scrollview content size: (123.0, 791.0)
=====
Frames in WebView's ScrollView:
[(2.0, 382.0, 121.25, 67.5), (2.0, 719.5, 121.25, 67.5)]
=====
like image 92
SlimeBaron Avatar answered Sep 30 '22 23:09

SlimeBaron