Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to determine the content size of a WKWebView?

I am experimenting with replacing a dynamically allocated instance of UIWebView with a WKWebView instance when running under iOS 8 and newer, and I cannot find a way to determine the content size of a WKWebView.

My web view is embedded within a larger UIScrollView container, and therefore I need to determine the ideal size for the web view. This will allow me to modify its frame to show all of its HTML content without the need to scroll within the web view, and I will be able to set the correct height for the scroll view container (by setting scrollview.contentSize).

I have tried sizeToFit and sizeThatFits without success. Here is my code that creates a WKWebView instance and adds it to the container scrollview:

// self.view is a UIScrollView sized to something like 320.0 x 400.0. CGRect wvFrame = CGRectMake(0, 0, self.view.frame.size.width, 100.0); self.mWebView = [[[WKWebView alloc] initWithFrame:wvFrame] autorelease]; self.mWebView.navigationDelegate = self; self.mWebView.scrollView.bounces = NO; self.mWebView.scrollView.scrollEnabled = NO;  NSString *s = ... // Load s from a Core Data field. [self.mWebView loadHTMLString:s baseURL:nil];  [self.view addSubview:self.mWebView]; 

Here is an experimental didFinishNavigation method:

- (void)webView:(WKWebView *)aWebView                              didFinishNavigation:(WKNavigation *)aNavigation {     CGRect wvFrame = aWebView.frame;     NSLog(@"original wvFrame: %@\n", NSStringFromCGRect(wvFrame));     [aWebView sizeToFit];     NSLog(@"wvFrame after sizeToFit: %@\n", NSStringFromCGRect(wvFrame));     wvFrame.size.height = 1.0;     aWebView.frame = wvFrame;     CGSize sz = [aWebView sizeThatFits:CGSizeZero];     NSLog(@"sizeThatFits A: %@\n", NSStringFromCGSize(sz));     sz = CGSizeMake(wvFrame.size.width, 0.0);     sz = [aWebView sizeThatFits:sz];     NSLog(@"sizeThatFits B: %@\n", NSStringFromCGSize(sz)); } 

And here is the output that is generated:

2014-12-16 17:29:38.055 App[...] original wvFrame: {{0, 0}, {320, 100}} 2014-12-16 17:29:38.055 App[...] wvFrame after sizeToFit: {{0, 0}, {320, 100}} 2014-12-16 17:29:38.056 App[...] wvFrame after sizeThatFits A: {320, 1} 2014-12-16 17:29:38.056 App[...] wvFrame after sizeThatFits B: {320, 1} 

The sizeToFit call has no effect and sizeThatFits always returns a height of 1.

like image 643
Mark Smith Avatar asked Dec 16 '14 22:12

Mark Smith


People also ask

What browser is WKWebView?

WKWebView. WKWebView was introduced in iOS 8 allowing app developers to implement a web browsing interface similar to that of mobile Safari. This is due, in part, to the fact that WKWebView uses the Nitro Javascript engine, the same engine used by mobile Safari.

How do you evaluate JavaScript in WKWebView?

How to run JavaScript on a WKWebView with evaluateJavaScript() Using evaluateJavaScript() you can run any JavaScript in a WKWebView and read the result in Swift. This can be any JavaScript you want, which effectively means you can dig right into a page and pull out any kind of information you want.

What is a WKWebView?

A WKWebView object is a platform-native view that you use to incorporate web content seamlessly into your app's UI. A web view supports a full web-browsing experience, and presents HTML, CSS, and JavaScript content alongside your app's native views.

What is the difference between UIWebView and WKWebView?

Unlike UIWebView, which does not support server authentication challenges, WKWebView does. In practical terms, this means that when using WKWebView, you can enter site credentials for password-protected websites.


2 Answers

I think I read every answer on this subject and all I had was part of the solution. Most of the time I spent trying to implement KVO method as described by @davew, which occasionally worked, but most of the time left a white space under the content of a WKWebView container. I also implemented @David Beck suggestion and made the container height to be 0 thus avoiding the possibility that the problem occurs if the container height is larger that that of the content. In spite of that I had that occasional blank space. So, for me, "contentSize" observer had a lot of flaws. I do not have a lot of experience with web technologies so I cannot answer what was the problem with this solution, but i saw that if I only print height in the console but do not do anything with it (eg. resize the constraints), it jumps to some number (e.g. 5000) and than goes to the number before that highest one (e.g. 2500 - which turns out to be the correct one). If I do set the height constraint to the height which I get from "contentSize" it sets itself to the highest number it gets and never gets resized to the correct one - which is, again, mentioned by @David Beck comment.

After lots of experiments I've managed to find a solution that works for me:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {     self.webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in         if complete != nil {             self.webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { (height, error) in                 self.containerHeight.constant = height as! CGFloat             })         }          }) } 

Of course, it is important to set the constraints correctly so that scrollView resizes according to the containerHeight constraint.

As it turns out didFinish navigation method never gets called when I wanted, but having set document.readyState step, the next one (document.body.offsetHeight) gets called at the right moment, returning me the right number for height.

like image 67
IvanMih Avatar answered Sep 20 '22 04:09

IvanMih


You could use Key-Value Observing (KVO)...

In your ViewController:

- (void)viewDidLoad {     ...     [self.webView.scrollView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil]; }   - (void)dealloc {     [self.webView.scrollView removeObserver:self forKeyPath:@"contentSize" context:nil]; }   - (void)observeValueForKeyPath:(NSString *)keyPath                       ofObject:(id)object                         change:(NSDictionary *)change                        context:(void *)context {     if (object == self.webView.scrollView && [keyPath isEqual:@"contentSize"]) {         // we are here because the contentSize of the WebView's scrollview changed.          UIScrollView *scrollView = self.webView.scrollView;         NSLog(@"New contentSize: %f x %f", scrollView.contentSize.width, scrollView.contentSize.height);     } } 

This would save the use of JavaScript and keep you in the loop on all changes.

like image 30
davew Avatar answered Sep 19 '22 04:09

davew