WKNavigation delegate functions calling too late - swift

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.

Related

Distinguish client-side redirects from ajax requests in WKWebView

I'm writing a command line tool that uses WKWebView to capture screenshots of webpages. To do this, I have to ensure that the page is fully loaded, including all client-side redirects, before capturing the screenshot.
In general, this happens automatically, and I just have to wait for webView(_:didFinish:) to be called. However sometimes redirection happens after webView(_:didFinish:) is called for the original URL (e.g. all Google search result links).
To handle this, I check for new requests after loading is complete via webView(_:decidePolicyFor navigationAction:) and repeatedly call webView.load on the new requests until no more are generated.
The problem is that of course websites make lots of requests after a page is fully loaded that aren't redirections, e.g. sending tracking data. So I end up calling webView.load on those requests instead, and instead of screenshotting e.g. a blogpost, I end up screenshotting the URL that an embedded tracking script sent data to, which visually is of course just a blank page.
Is there any way I can distinguish client-side redirect requests from these background ajax requests? Or failing that maybe some other way to follow redirects without calling webView.load on each new request?
Here is a functioning simplified version of my code:
import WebKit
#MainActor
class WebContainer: NSObject {
private lazy var webView: WKWebView = {
let webView: WKWebView = WKWebView()
webView.navigationDelegate = self
return webView
}()
private var redirectURL: URL? // set every time decidePolicyFor navigationAction is called
private var loadedURL: URL? // set when didFinish is called
private var continuation: UnsafeContinuation<Void, Error>?
private func load(request: URLRequest) async throws -> Int {
try await withUnsafeThrowingContinuation { continuation in // required in the absence of an event loop, as this is a command line tool
self.continuation = continuation
webView.load(request)
}
}
private func takeScreenshot() async throws -> Data? {
// get data from webView and do stuff
}
private func checkRedirect() async -> Bool {
try! await Task.sleep(for: .seconds(0.1)) // small delay to wait for new requests
if redirectURL != loadedURL { // checks if redirectURL has been set to something new
return true
}
return false
}
func generateData(type: DataType, request: URLRequest) async throws -> Data? {
// Load website
try await load(request: request)
// Check for redirects after loading completed
while await checkRedirect() == true {
let redirectRequest: URLRequest = URLRequest(url: redirectURL!)
try await load(request: redirectRequest)
}
// Process & return data
return try await takeScreenshot()
}
}
extension WebContainer: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
redirectURL = navigationAction.request.url // if a new request is initiated after webView(_:didFinish:) is called, this will set redirectURL to the new URL
return WKNavigationActionPolicy.allow
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
loadedURL = webView.url
continuation?.resume(returning: ())
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
navigationFailed = true
continuation?.resume(throwing: error)
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
navigationFailed = true
continuation?.resume(throwing: error)
}
}

WKWebView Navigation Delegate Not Called

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.

Check if UIWebView is done loading in Swift

I am making an application that loads a UIWeb View and runs a line of code the moment the webpage is completely loaded.
I am using code like this to load the webpage:
let links = URL (string: "https://example.com")
let loadpage = URLRequest(url: links!)
webPage.loadRequest(load)
How can I run a piece of code the moment the webpage is done? I have tried some examples online but the webpage stops loading while checking if it has loaded?
Thanks!
As stated in the documentation of UIWebView, for apps targeting iOS8 or later you should be using WKWebView instead.
When using a WKWebView, you can make your class conform to WKNavigationDelegate and the delegate method func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) will be called once the webview finishes loading a network request.
If for some reason you are still targeting iOS7 or any previous iOS versions and hence the use of a UIWebView, you can use UIWebViewDelegate's func webViewDidFinishLoad(_ webView: UIWebView) method instead.
Skeleton code for the UIWebView solution:
class WebVC: UIViewController {
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
webView.delegate = self
let url = URL(string: "https://example.com")
let request = URLRequest(url: url)
webView.loadRequest(request)
}
}
extension WebVC: UIWebViewDelegate {
func webViewDidFinishLoad(_ webView: UIWebView) {
print("Finished loading")
}
}

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")
}

How can I check If the valid page was loaded in the WKWebView?

That's what I'm doing:
Loading the page in WKWebView
Pressing the button on the page
As a result I can get either valid URL, either invalid URL. How do I check if I've got the valid one? I need something like URLThatWasLoadedInWKWebView == "myValidURL"?
var webView: WKWebView!
var webUrl="google.com"
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: webUrl)!
let urlRequest = URLRequest(url: url)
webView.load(urlRequest)
view.insertSubview(webView, at: 0)
}
After I clicked on the button, I need to check that the page has loaded the valid "mail.google.com"
If you have your view controller conform to the WKNavigationDelegate then you can use those delegate functions to get the information you're looking for.
Inside this delegate function:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)
You could check the webView.url which shows you the url that the web view just loaded. You could use that to check against whatever url you are trying to check against.