Reputation: 1078
Goal: To take a screenshot of WKWebView after the website finished loading
Method employed:
Created an extension method called screen capture() that takes image of WKWebView
Made my UIViewController to implement WKNavigationDelegate
Set the wkwebview.navigationDelegate = self ( in the UIViewController init)
Implemented the didFinishNavigation delegation func in UIViewcontroller to call screen capture extension method for WKWebView
func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) { let img = webView.screenCapture() }
Questions:
What am I missing here? I looked at all possible delegate functions for WKWebView and nothing else seem to represent the completion of content loading in WKWebView. Would appreciate help on if there is a work around
Update: Adding screenshot code that I am using to take a screenshot for web view
class func captureEntireUIWebViewImage(webView: WKWebView) -> UIImage? {
var webViewFrame = webView.scrollView.frame
if (webView.scrollView.contentSize != CGSize(width: 0,height: 0)){
webView.scrollView.frame = CGRectMake(webViewFrame.origin.x, webViewFrame.origin.y, webView.scrollView.contentSize.width, webView.scrollView.contentSize.height)
UIGraphicsBeginImageContextWithOptions(webView.scrollView.contentSize, webView.scrollView.opaque, 0)
webView.scrollView.layer.renderInContext(UIGraphicsGetCurrentContext())
var image:UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
webView.scrollView.frame = webViewFrame
return image
}
return nil
}
Upvotes: 39
Views: 38346
Reputation: 421
Most of these answers likely won't give you the results you're looking for.
Let the html document tell you when it's loaded.
Here is how it is done.
script message handler delegate
@interface MyClass : UIView <WKScriptMessageHandler>
Initialize the WKView to handle whatever event you'd like (e.g. window.load)
WKWebView* webView = yourWebView;
NSString* jScript = @"window.addEventListener('load', function () { window.webkit.messageHandlers.loadEvent.postMessage('loaded');})";
WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
[webView.configuration.userContentController addScriptMessageHandler:self name:@"loadEvent"];
[webView.configuration.userContentController addUserScript:wkUScript];
Handle the delegate message.
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
NSString* name = message.name;
if([name compare:@"loadEvent"] == 0)
{
}
}
Upvotes: 1
Reputation: 1568
For those still looking for an answer to this, the marked answer is BS, he just forced his way into getting it accepted.
Using property,
"loading"
and
webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!)
both do the same thing, indicate if the main resource is loaded.
Now, that does not mean the entire webpage/website is loaded, because it really depends on the implementation of the website. If it needs to load scripts and resources (images, fonts etc) to make itself visible, you'll still see nothing after the navigation is completed, because the network calls made by the website are not tracked by the webview, only the navigation is tracked, so it wouldn't really know when the website loaded completely.
Upvotes: 40
Reputation: 2889
You can inject JavaScript into the web view to either wait for onDOMContentLoaded or check the document.readyState state. I have an app that has been doing this for years, and it was the only reliable way to wait for the DOM to be populated. If you need all the images and other resources to be loaded, then you need to wait for the load event using JavaScript. Here are some docs:
https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState
Upvotes: 0
Reputation: 535201
WKWebView doesn't use delegation to let you know when content loading is complete (that's why you can't find any delegate method that suits your purpose). The way to know whether a WKWebView is still loading is to use KVO (key-value observing) to watch its loading
property. In this way, you receive a notification when loading
changes from true
to false
.
Here's a looping animated gif showing what happens when I test this. I load a web view and respond to its loading
property through KVO to take a snapshot. The upper view is the web view; the lower (squashed) view is the snapshot. As you can see, the snapshot does capture the loaded content:
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
if (self->_webKitView.isLoading == true) {
NSLog(@"Still loading...");
}else {
NSLog(@"Finished loading...");
[timer invalidate];
dispatch_async(dispatch_get_main_queue(), ^{
[self->_activityIndicator stopAnimating];
});
}
}];
Upvotes: 12
Reputation: 584
Lots of hand waiving in here, for incomplete solutions. The observer on "loading" is not reliable because when it's switched to NO, the layout hasn't happened yet and you can't get an accurate reading on page size.
The injection of JS into the view to report page size can also be problematic depending on actual page content.
The WKWebView uses, obviously, a scroller (a "WKWebScroller" subclass to be exact). So, your best bet is to monitor that scroller's contentSize.
- (void) viewDidLoad {
//...super etc
[self.webKitView.scrollView addObserver: self
forKeyPath: @"contentSize"
options: NSKeyValueObservingOptionNew
context: nil];
}
- (void) dealloc
{
// remove observer
[self.webKitView.scrollView removeObserver: self
forKeyPath: @"contentSize"];
}
- (void) observeValueForKeyPath: (NSString*) keyPath
ofObject: (id) object
change: (NSDictionary<NSKeyValueChangeKey,id>*) change
context: (void*) context
{
if ([keyPath isEqualToString: @"contentSize"])
{
UIScrollView* scroller = (id) object;
CGSize scrollFrame = scroller.contentSize;
NSLog(@"scrollFrame = {%@,%@}",
@(scrollFrame.width), @(scrollFrame.height));
}
}
Watch out for the contentSize: it gets triggered A LOT. If your webView is embedded into another scrolling area (like if you're using it as a form element) then when you scroll your entire view, that subview webView will trigger the changes for the same values. So, make sure you dont resize needlessly or cause needless refreshes.
This solution tested on iOS 12 on iPad Air 2 sim off High Sierra XCode 10.
Upvotes: 8
Reputation: 39181
Here is how I solved it:
class Myweb: WKWebView {
func setupWebView(link: String) {
let url = NSURL(string: link)
let request = NSURLRequest(URL: url!)
loadRequest(request)
addObserver(self, forKeyPath: "loading", options: .New, context: nil)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
guard let _ = object as? WKWebView else { return }
guard let keyPath = keyPath else { return }
guard let change = change else { return }
switch keyPath {
case "loading":
if let val = change[NSKeyValueChangeNewKey] as? Bool {
if val {
} else {
print(self.loading)
//do something!
}
}
default:break
}
}
deinit {
removeObserver(self, forKeyPath: "loading")
}
}
Update Swift 3.1
override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let _ = object as? WKWebView else { return }
guard let keyPath = keyPath else { return }
guard let change = change else { return }
switch keyPath {
case "loading":
if let val = change[NSKeyValueChangeKey.newKey] as? Bool {
//do something!
}
default:
break
}
}
Upvotes: 8
Reputation: 28529
It's not a good choice to check if the page content loaded from swift or objective-c especially for very complex page with many dynamic content.
A better way to inform you ios code from webpage's javascript. This make it very flexible and effective, you can notify ios code when a dom or the page is loaded.
You can check my post here for further info.
Upvotes: 1