Do something after WKWebView has finished loading and scraping specific page - swift

what I'm trying to do is perform an action only after the WKWebView I'm using has finished loading and scraping the page.
Here's the code for the webView:
let webView = WKWebView()
let url = URL(string: "https://web.spaggiari.eu/home/app/default/menu_webinfoschool_genitori.php?custcode=")!
let request = URLRequest(url: url)
webView.load(request)
What I want to do is show the button to "Connect" only after the process has fished (that specific process and not when the webView finishes loading every time). For now I'm using DispatchQueue to wait for seconds hoping the page loads in time.
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4)) {
UIView.animate(withDuration: 0.8, animations: {
self.registerButton.alpha = 1
self.registerButton.isEnabled = true
})
UIView.animate(withDuration: 0.8, delay: 0.4, animations: {
self.blurButton.alpha = 1
})
}
I'm using Xcode with Swift3

this code indicates that page fully loaded, may this help you
func webpageFullyLoaded(_ str: String) {
if (str == "complete") || (str == "interactive") {
print("page completely loaded")
}
}
func webViewDidFinishLoad(_ webView: UIWebView) {
webpageFullyLoaded(webView.stringByEvaluatingJavaScript(from: "document.readyState"))
}

WKWebView has this delegate
/*! #abstract The web view's navigation delegate. */
weak open var navigationDelegate: WKNavigationDelegate?
WKNavigationDelegate :
A class conforming to the WKNavigationDelegate protocol can provide
methods for tracking progress for main frame navigations and for deciding
policy for main frame and subframe navigations
This delegate has a method, that might help you
/*! #abstract Invoked when a main frame navigation completes.
#param webView The web view invoking the delegate method.
#param navigation The navigation.
*/
#available(iOS 8.0, *)
optional public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)
So you can try this
func loadPage() {
let webView = WKWebView()
webView.navigationDelegate = self
let url = URL(string: "https://web.spaggiari.eu/home/app/default/menu_webinfoschool_genitori.php?custcode=")!
let request = URLRequest(url: url)
webView.load(request)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// Do your work here
}
Also make sure your self here conforms to WKNavigationDelegate.
Update
To handle loading of different web pages in the same WKWebView you can,
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Swift.Void) {
print("Page Being loaded is \(navigationAction.request), you can do your work here")
decisionHandler(.allow)
}
You can read more about what you can do in the navigation delegate here

Related

How to use WKWebView in a command line app

I'm trying to write a command line tool that takes a screenshot of a given webpage using WKWebView. The problem is that WKNavigationDelegate methods aren't being called. This is what I have:
import WebKit
class Main: NSObject {
let webView: WKWebView = WKWebView()
func load(request: URLRequest) {
webView.navigationDelegate = self
webView.load(request)
}
}
extension Main: WKNavigationDelegate {
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
print("Did start")
}
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
print("Did commit")
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("Did finish")
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
print("Did fail")
}
}
let main: Main = Main()
let input: String = CommandLine.arguments[1]
if let url: URL = URL(string: input) {
let request: URLRequest = URLRequest(url: url)
main.load(request: request)
} else {
print("Invalid URL")
}
Almost all examples I've found involve using WKWebView in a view controller. My guess is that in the command line, the app exits before the webpage finishes loading, but I'm not sure how to prevent that from happening.
I did find this example of a command line tool using WKWebView. The author uses RunLoop.main.run(), which to my understanding effectively simulates the event loop of a UI app? That allows the webpage to load, but I'm looking for a different solution because I want the app to behave like a normal command line tool and exit on its own after running. For example, is there some way to use async/await with WKWebView.load() much like with URLSession?
I ended up solving this problem using continuation. In short, I wrap webView.load() in a continuation and then call continuation.resume() in one of the WKNavigationDelegate methods. That allows me to treat webView.load() as an async task; the continuation determines its runtime. I took this solution entirely from the example in this blog post.
Here's a barebones implementation of this solution:
import WebKit
#MainActor
class WebContainer: NSObject {
lazy var webView: WKWebView = {
let webView: WKWebView = WKWebView()
webView.navigationDelegate = self
return webView
}()
var continuation: UnsafeContinuation<Void, Error>?
func load(request: URLRequest) async throws -> Int {
try await withUnsafeThrowingContinuation { continuation in
self.continuation = continuation
webView.load(request)
}
}
}
extension WebContainer: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
continuation?.resume(returning: ())
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
continuation?.resume(throwing: error)
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
continuation?.resume(throwing: error)
}
}
To load a website using the above implementation, simply run:
let webContainer: WebContainer = WebContainer()
let request: URLRequest = // insert URLRequest here
try await webContainer.load(request: request)
Practically speaking this implementation doesn't handle redirects reliably as some redirects are initiated after didFinish is called. I have a partial solution to that which however runs into other problems. Since all this is out of scope for this question, if anyone's interested please refer to this other question.

SwiftUI: Cannot open window.open() javascript call on page in wkwebview with ui delegate createWebViewWithConfiguration

I have gone through a lot of different StackOverflow threads & Apple Developer pages to figure out what is going on here. I believe I am so close.
I have a WebView that runs on a site for a bit. Eventually the user can click a link that would open a new tab. It is opened via a window.open() call in JavaScript in the HTML.
However, on SwiftUI nothing runs, and I have set-up what should be the proper UIDelegate. This function never gets entered as I step through the call to WebView.
My question is: How can I make sure the UIDelegate is properly set? Where does this occur? Do I have the right spot?
My implementation (shortened):
struct WebView: UIViewRepresentable {
var url: URL
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> WKWebView {
// Enable javascript in WKWebView to interact with the web app
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
preferences.javaScriptCanOpenWindowsAutomatically = true
let configuration = WKWebViewConfiguration()
// Here "iOSNative" is our interface name that we pushed to the website that is being loaded
// configuration.userContentController.add(self.makeCoordinator(), name: "iOSNative")
configuration.preferences = preferences
let webView = WKWebView(frame: CGRect.zero, configuration: configuration)
// webView.navigationDelegate = context.coordinator
// webView.uiDelegate = context.coordinator
// webView.allowsBackForwardNavigationGestures = true
// webView.scrollView.isScrollEnabled = true
return webView
}
func updateUIView(_ webView: WKWebView, context: Context) {
let request = URLRequest(url: url)
webView.uiDelegate = context.coordinator
webView.navigationDelegate = context.coordinator
webView.load(request)
}
class Coordinator : NSObject, WKUIDelegate, WKNavigationDelegate {
var parent: WebView
var webViewNavigationSubscriber: AnyCancellable? = nil
init(_ uiWebView: WebView) {
// uiWebView.uiDelegate = self
self.parent = uiWebView
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
// Suppose you don't want your user to go a restricted site
if navigationAction.targetFrame == nil {
if let url = navigationAction.request.url {
UIApplication.shared.open(url)
decisionHandler(.allow)
return
}
}
func webView(webView: WKWebView!, createWebViewWith configuration: WKWebViewConfiguration!, forNavigationAction navigationAction: WKNavigationAction!, windowFeatures: WKWindowFeatures!) -> WKWebView! {
// if navigationAction.targetFrame == nil {
// UIApplication.shared.open(navigationAction.request.url!)
// }
// return nil
if (!(navigationAction.targetFrame?.isMainFrame ?? false)) {
webView.load(navigationAction.request)
}
return nil
}
func webView(webView: WKWebView!, createWebViewWithConfiguration configuration: WKWebViewConfiguration!, forNavigationAction navigationAction: WKNavigationAction!, windowFeatures: WKWindowFeatures!) -> WKWebView! {
if navigationAction.targetFrame == nil {
UIApplication.shared.open(navigationAction.request.url!)
}
return nil
}
}
}
My thoughts:
Am I assigning uiDelegate & navigationDelegate in the right spots? It doesn't seem that my createWebViewWith delegate ever gets called. The app opens HTML links in tags with target = "_blank", it's just this one that is called via window.open(x) in javascript.
My resources:
WKWebView and window.open
Window.open() is not working in WKWebView
https://developer.apple.com/forums/thread/664267
https://developer.apple.com/forums/thread/68427

How to Autofill forms in WKWebView Using User Defaults or JavaScript

Hello Swift Developers!.
I am new to swift programming and need little help.
I am developing a very simple app, that should simply load the web using WkWebView and autofill the shipping form it have in on of its page.
I have successfully managed to fetch the page in webView(WkWebView).
First it loads this url https://www.adidas.com/us, after selecting the item in to cart it gets to this delivery page https://www.adidas.com/us/delivery where we have to fill this form. before this, no login information needed.
I am trying to make it done with both UIWebView and WkWebView but to no avail, here's my code, UIWebView part is commented.
class ViewController: UIViewController, WKUIDelegate {
#IBOutlet weak var uiWebView: UIWebView!
var webView: WKWebView!
let url: String = "https://www.adidas.com/us/delivery"
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
//WKWebView programatically so it can run below iOS 11
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
view = webView
if let safeUrl = URL(string: url){
let request = URLRequest(url: safeUrl)
webView.load(request)
}
// uiWebView.loadRequest(NSURLRequest(url: NSURL(string: url)! as URL) as URLRequest)
// let result = uiWebView.stringByEvaluatingJavaScript(from: "document.title")
// print("result is: \(result!)")
self.webView.evaluateJavaScript("document.getElementById('di-id-cab9a55c-9d253ce3').value = 'Hello'") { (result, error) in
print(result) //This will Print Hello
}
}
// func webViewDidFinishLoad(_ webView: UIWebView) {
//
// let email = defaults.string(forKey: "EMAIL")
// let password = defaults.string(forKey: "Pass")
//
// let fillForm = "document.getElementById('f_707d6a95-3ef9-4b76-a162-9361b4ef7d4d').value = \(password)"
// webView.stringByEvaluatingJavaScript(from: fillForm)
// }
}
And Here's the screenshot of inspect element of First Name field.
After this I would use user default for autofill data. that step I know how to do just stuck here!
Can I have any helpful code snippet or suggestion please? I am really stuck at this point!
Thanks in advance
First: stop using UIWebView since it's deprecated from one side and stop supporting by AppStore from Dec 2020.
You should set navigationDelegate to you WKWebView and set your values to the fields on webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) when the page is loaded e.g.:
override func viewDidLoad() {
super.viewDidLoad()
...
webView?.navigationDelegate = self
...
}
extension ViewController : WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// Set a delay for dynamic pages
//DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
let fillForm = "document.getElementsByName('firstName')[0].value = 'My Name'"
webView.evaluateJavaScript(fillForm, completionHandler: nil)
//}
}

The WKWebView was invalidated" UserInfo={NSLocalizedDescription=The WKWebView was invalidated

I am trying to retrieve html using webView. I don't want to write my webView code in the view file therefore trying to implement it in a different class. Below is my code.
class BlobHelper: NSObject,WKNavigationDelegate{
let webView = WKWebView()
func getLyrics(){
let url = URL(string: "https://Blob")!
let request = URLRequest(url: url)
webView.load(request)
webView.evaluateJavaScript("document.getElementsByTagName('html')[0].innerHTML;", completionHandler: {(value,error)in
print(value)
print(error)
})
}
func webView(_ webView: WKWebView,didFinish navigation: WKNavigation!) { print("loaded") }
When I execute the code I get Optional(Error Domain=WKErrorDomain Code=3 "The WKWebView was invalidated" UserInfo={NSLocalizedDescription=The WKWebView was invalidated}).
What I tried
I was able to retrieve html using static but couldn't get the didFinish to call after loading.
Any help is appreciated.
class ViewController: UIViewController, WKNavigationDelegate {
var webView = WKWebView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let configuration = WKWebViewConfiguration()
self.webView = WKWebView(frame: .zero, configuration: configuration)
webView.navigationDelegate = self
let url = URL(string: "https://stackoverflow.com")!
let request = URLRequest(url: url)
webView.load(request)
self.view = webView
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.getElementsByTagName('html')[0].innerHTML;", completionHandler: { (value,error)in
print(value)
})
}}

WKWebview: how to prevent Laravel Page links from opening in safari?

I am new to swift.
I built a web application and serving it as an iOS app using WKWebView (this is for internal company use only).
In the web app, I used laravel pagination, this works properly on a browser and android webview.
what happens on iOS is, when I click the page number link inside the WKWebView, the link opens up in safari. What I want to do is open it on the same Webview.
This is my code inside ViewController.swift
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
var webView : WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = URL(string: "https://domain-online.asia")!
let urlRequest = URLRequest(url: url)
webView.load(urlRequest)
let refresh = UIBarButtonItem(barButtonSystemItem: .refresh, target: webView, action: #selector(webView.reload))
toolbarItems = [refresh]
navigationController?.isToolbarHidden = false
}
override func loadView() {
let source: String = "var meta = document.createElement('meta');" +
"meta.name = 'viewport';" +
"meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';" +
"var head = document.getElementsByTagName('head')[0];" + "head.appendChild(meta);";
let script: WKUserScript = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
let userContentController: WKUserContentController = WKUserContentController()
let conf = WKWebViewConfiguration()
conf.userContentController = userContentController
userContentController.addUserScript(script)
webView = WKWebView(frame: CGRect.zero, configuration: conf)
webView.navigationDelegate = self
view = webView
}
func webView(webView: WKWebView!, createWebViewWithConfiguration configuration: WKWebViewConfiguration!, forNavigationAction navigationAction: WKNavigationAction!, windowFeatures: WKWindowFeatures!) -> WKWebView! {
if navigationAction.targetFrame == nil {
webView.load(navigationAction.request)
}
return nil
}}
Implement decidePolicyFor and set policy as below,
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
guard navigationAction.navigationType == .linkActivated,
let url = navigationAction.request.url,
url.host?.contains("domain-online.asia") == false,
UIApplication.shared.canOpenURL(url) else {
decisionHandler(.allow)
return
}
UIApplication.shared.open(url)
decisionHandler(.cancel)
}