Loading an image ad inside a WKWebView and adjusting its dimensions based on the content can be tricky, especially if the document.body.scrollHeight doesn't provide accurate results. Here are some strategies you can use to dynamically size the WKWebView to fit the content, particularly images:
- Use Message Handlers for Dynamic Content Size
One effective way to get the dimensions of loaded images is to use JavaScript message handlers to communicate size information back to the Swift side.
Steps:
Configure a Message Handler:
Set up a message handler in your WKWebView to receive size information from JavaScript.
import WebKit
class WebViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
// Add message handler
webView.configuration.userContentController.add(self, name: "imageSizeHandler")
// Load the image ad
if let url = URL(string: "https://example.com/your-image-ad.html") {
let request = URLRequest(url: url)
webView.load(request)
}
}
// WKNavigationDelegate method
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// Send JavaScript to get image sizes
let script = "var images = document.querySelectorAll('img'); var sizes = []; for (var i = 0; i < images.length; i++) { sizes.push({ width: images[i].offsetWidth, height: images[i].offsetHeight }); } window.webkit.messageHandlers.imageSizeHandler.postMessage(sizes);"
webView.evaluateJavaScript(script)
}
}
extension WebViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "imageSizeHandler", let sizes = message.body as? [[String: CGFloat]] {
// Adjust webView's frame based on received sizes
adjustWebViewSize(for: sizes)
}
}
func adjustWebViewSize(for sizes: [[String: CGFloat]]) {
// Assuming you want to fit the largest image
guard let largestImage = sizes.max(by: { $0["width"]! * $0["height"]! < $1["width"]! * $1["height"]! }) else { return }
let newHeight = largestImage["height"]! * (webView.frame.width / largestImage["width"]!)
webView.frame.size = CGSize(width: webView.frame.width, height: newHeight)
}
}
JavaScript to Measure Image Sizes:
The JavaScript embedded in the page calculates the dimensions of all images and sends them back via the message handler.
- Observe Content Size Changes
Alternatively, you can observe changes to the contentSize property of the WKWebView, although this might not always accurately reflect image sizes until they are fully loaded.
Steps:
override func viewDidLoad() {
super.viewDidLoad()
webView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "contentSize" {
// Adjust webView's frame based on new content size
webView.frame.size = change?[
.newKey
] as? CGSize ?? webView.frame.size
}
}
deinit {
webView.removeObserver(self, forKeyPath: "contentSize")
}
Considerations
Image Loading Delays: Images might load at different times, so sizes might be reported incrementally. Consider handling partial updates or waiting for a stable size report.
Complex Pages: If the page contains dynamic content or iframes, additional logic might be needed to capture all relevant images.
Performance: Continuously observing content size or executing JavaScript can impact performance. Balance between accuracy and efficiency.
By using message handlers, you can get more precise control over sizing the WKWebView based on the actual content it loads, particularly images. This approach should provide more reliable results than relying solely on document.body.scrollHeight.