WKWebView Navigation Delegate Not Called - swift

I am having difficulty getting a WKWebview to call its delegate methods, namely the did finish one.
I am attempting something similar to this post generating a pdf report using html loaded into a WKWebView;
WKWebView not calling navigation delegate methods
However trying the suggestion there, that doesn't work for me. I have a WKWebView made in a separate class to the View controller that will be calling its methods.
If I strip things right down and just try to simply load a website in the WebView to see if the delegate functions get called I get nothing.
Here is my test class;
import UIKit
import WebKit
class TestWebview: NSObject {
let webView = WKWebView ()
func loadWebsite () {
print ("LOAD TEST CALLED")
webView.navigationDelegate = self
let link = URL(string: "https://www.apple.com")!
let request = URLRequest(url: link)
webView.load(request)
}
}
extension TestWebview: WKNavigationDelegate {
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
print ("DID COMMIT")
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print ("FINISHED LOADING")
}
}
And here is how I am calling it in a separate view controller;
override func viewDidLoad() {
super.viewDidLoad()
let test = TestWebview ()
test.loadWebsite()
}
Although if I put a WKWebView as an outlet into a view controller using Storyboards then the delegate methods do get called. I am trying to avoid this however as I don't want to see a web view in the view controller and want to keep all the code for that separate in another class.
Am I missing something? Checking various other posts on Stack Overflow about this hasn't given me any solutions other than making sure the navigation delegate is being set, which I am. Any help would be greatly appreciated.

You need to move let test = TestWebview () to class level, otherwise this object is going to be evicted when viewDidLoad completes: the navigationDelegate is defined as weak, so it's not going to prevent it either.

Related

Swift NSObject class does not reliably respond to delegate method calls

I have had a couple of instances of this recently, so I thought I'd reach out to better understand if I am doing something wrong or iOS/Xcode is just being weird.
I have a utility class, which just inherits from NSObject. My utility class holds reference to a WKWebView, and is the webview's navigationDelegate.
For the life of me, I cannot get the utility class to respond to the navigationDelegate methods, UNLESS I breakpoint at the point of setting the delegate, print out something (anything, from my testing) to the console (yep, this is important. Just breakpointing isn't enough - I have to PRINT something). Then re-run, and it responds as normal.
I am very familiar with iOS development and delegation patterns, but this is behaviour I've seen before and never understood, so I wanted to better my understanding if it's something I am getting wrong.
Here is some example code of what I am doing. It's worth noting, that if I take the utility out of the equation and just use UIViewControllers as the navigationDelegate, everything works fine.
class ExampleUtility: NSObject, WKNavigationDelegate {
private weak var containerViewController: UIViewController?
private let webView: WKWebView
init(containerViewController: UIViewController) {
self.containerViewController = containerViewController
webView = WKWebView(frame: containerViewController.view.frame)
super.init()
setupWebView()
}
private func setupWebView() {
webView.navigationDelegate = self
containerViewController?.view.addSubview(webView)
}
func start() {
guard let url = URL(string: urlString) else {
return
}
let request = URLRequest(url: url)
webview.load(request)
}
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// This is never called, unless I print ANYTHING to the console
}
}
The fact that it responds after breakpointing and printing makes me thing its a bug of some sort? Also worth nothing that this isn't only on debug runs. It happens when not running from Xcode also.
Any help is greatly appreciated.
Please just keep ExampleUtility object as property in containerViewController.
Then it will work.
The reason you have this issue is, the webview instance is created in the ExampleUtility but the instance of Example Utility was already released since it is not property of containerViewController.
Please try and it will work surely.

WKNavigation delegate functions calling too late

I am loading two websites in two different web views and after loading website i am hiding some content of it which is working perfect using WKNavigationDelegate did finish method.
but issue is did finish function is calling about 90 sec after loading website in web view.
Function is working fine but i just want to know why this function is loading too late it should execute function right after loading website.
my code is
web_view.navigationDelegate = self as? WKNavigationDelegate
web_view.isUserInteractionEnabled = true
let request = URLRequest(url: url!)
self.web_view.load(request)
self.view.addSubview(self.web_view)
delegate method
extension urdu_HomeViewController : WKNavigationDelegate{
//enable javascript to remove vavigation from website
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let removeelementid = "javascript:(function() { " + "document.getElementsByClassName('td-header-menu-wrap-full td-container-wrap')[0].style.display=\"none\"; " + "})()"
webView.evaluateJavaScript(removeelementid) { (res, error) in
if error != nil
{
print("Error")
}
else
{
//print(res!)
}
}
}
WKNavigationDelegate didFinish method is called only after all subresources are loaded. So in your case it might be that some resource takes too much time to load and that delays calling didFinish.
You should rather use WKUserScript to execute your JS right after DOM is ready.

Swift 3 - Check if WKWebView has loaded page

My question:
How do I check if a page in WKWebView has fully loaded in Xcode using Swift 3?
This is my problem:
Webpage 1:
From this page I load Webpage 2
Webpage 2:
I need to get html data from Webpage 2, but when I print the HTML data I get HTML data of webpage 1, which I don't want. But when I print HTML data 2 seconds later it gives me the right HTML data.
I need to know whether or not a page in WKWebView did finish loading. I can see in the WebView it is loaded and also the progressbar is fully loaded, but when I print html data of the page I get html data of previous page, which is not what I want. Only if I wait a second it gives the right data, probably cause Webpage 2 is loaded.
So how do I let Xcode to print html when the next page is totally loaded?
I have tried several methods:
detect WKWebView finish loading
Call JavaScript function from native code in WKWebView
Maybe I can use:
if webView.isloading { get }
but I don't know how to implement this method and if it should work.
I have tried several methods from Stack but these are not working for me or outdated.
Do you guys know a solution for this problem in Swift 3?
Thanks!
Answer (Big thanks to #paulvs )
To check if your WKWebView has loaded easily implement the following method:
import WebKit
import UIKit
class ViewController: UIViewController, WKNavigationDelegate {
let webView = WKWebView()
func webView(_ webView: WKWebView,
didFinish navigation: WKNavigation!) {
print("loaded")
}
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://www.yourwebsite.com/") !
let request = URLRequest(url: url)
webView.navigationDelegate = self
webView.load(request)
// Do any additional setup after loading the view, typically from a nib.
}
}
Add WKNavigationDelegate to class
Add:
func webView(_ webView: WKWebView,didFinish navigation: WKNavigation!) { print("loaded") }
Result: It will print "loaded" in the console everytime the WKWebView has finished loading the page. This was excactly what I was looking for, so again a big thanks to Paulvs!
Set delegate > WKNavigationDelegate
Objective-C
// Start loading WKWebView
-(void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
NSLog(#"Start loading");
}
//Finished loading WKWebView
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
NSLog(#"End loading");
}
Swift 4.2
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
print("Start loading")
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("End loading")
}

What am I supposed to pass to scrollToEndOfDocument(_:)?

I’m trying to get a WKWebView to scroll down automatically when its content changes. I added this function to my WKNavigationDelegate, which is a subclass of NSViewController:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("Finished loading web view.")
webView.scrollToEndOfDocument(self)
}
It crashes:
-[WKWebView scrollToEndOfDocument:]: unrecognized selector sent to instance 0x6080001a07e0
I guess self is not what I should be passing to scrollToEndOfDocument. I also tried to pass webView or nil instead, with the same result. What value should I pass?
N.B. I tried self because Apple’s docs say:
Declaration
func scrollToEndOfDocument(_ sender: Any?)
Parameters
sender
Typically the object that invoked this method.
You may have to do it via JavaScript:
let script = "window.scrollTo(0, document.body.scrollHeight)"
webView.evaluateJavaScript(script)
This is for old-school WebView; leaving for posterity:
Try calling scrollToEndOfDocument: on the WebView's main frame's document view:
webView.mainFrame.frameView.documentView.scrollToEndOfDocument(nil)
(What you pass in as sender shouldn't matter and has no bearing on the error you're seeing.)

WKNavigationDelegate only works if set to self

I am writing a Swift Hybrid application and I need to be able to know when my WKWebView has finished loading a request. I am attempting to use WKNavigationDelegate to achieve this. However, the only way I can get the events to fire is if I set it this way:
webView.navigationDelegate = self
The problem is I have some data that I want associated with my request, so I created a custom class that implements WKNavigationDelegate that looks like this:
class MyNavigationDelegate : NSObject, WKNavigationDelegate {
init(...) {
//set local variables with passed args
}
func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
println("finished loading")
}
}
then I assign it like this:
webView.navigationDelegate = MyNavigationDelegate(my, arguments, here)
webView.loadRequest(...)
The page is loading so there is no problem with how I'm loading it. What am I missing?
The delegate is a weak property, so make sure that you're holding on to the delegate class instance elsewhere -- it's likely getting deallocated before it even gets used. Meanwhile, self is not getting deallocated, so it is working.