WKWebView decidePolicyFor gets called after page loaded - ios

I try to catch the url about to load in WKWebView before it loads. Based on documents decidePolicyFor navigationAction (WKNavigationDelegate) should do the job but my problem is this delegate gets called after new url get loaded not before that.
here is the extension I wrote.
extension MyWebViewController: WKNavigationDelegate {
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
guard let navigationURL = navigationAction.request.url else {
decisionHandler(.allow)
return
}
let forbiddenUrlPattern = Configuration.current.links.forbiddenUrlPattern
if forbiddenUrlPattern.matches(url: navigationURL) {
decisionHandler(.cancel)
showFullScreenError(error: .forbidden)
return
}
// Default policy is to allow navigation to any links the subclass doesn't know about
decisionHandler(.allow)
}
}
*PS the matches extension checks the pattern and it works fine.
now the problem is that the content of forbiddenUrl showed for a time before this delegate func gets called and then the error page comes to screen, and if I close it the last visible webPage is from forbidden link pattern.
is there any way to understand about the link before actually loading it in webView?
I am using Xcode 11.2.1 & Swift 5.0

I wrote the answer I found here that it may help someone else as well.
After lots of struggle I found out decisionHandler wont get called if the url is relative (not absolute urls).
So why decisionHandler got called after loading that page?
the answer I found is: when we have urls like href:"/foo/ba" then after calling that url, it will load and resolve as www.domain.com/foo/ba and only then desicionHandler got called.
Also didCommit only called once when I wanted to load the url for the first time in webView.
so the solution helped me was to add an observer to webView
webView.addObserver(self, forKeyPath: "URL", options: [.new,.old], context: nil)
and
override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
/// This observer is in addition to the navigationAction delegate to block relative urls and intrrupt them and do native
/// action if possible.
if let newValue = change?[.newKey] as? URL, let oldValue = change?[.oldKey] as? URL, newValue != oldValue {
let forbiddenUrlPattern = Configuration.current.links.forbiddenUrlPattern
if forbiddenUrlPattern.matches(url: newValue) {
showFullScreenError(error: .forbidden)
return
}
/// These two action needed to cancel the webView loading the offerDetail page.
/// otherwise as we stop loading the about to start process webView will show blank page.
webView.stopLoading()
///This is small extension for make webView go one step back
webView.handleBackAction(sender: newValue)
return
}
}
}
So this observer in addition to decisionHandler would cover both absolute and relative urls any one wants to listen and take action if needed.

Related

how do I perform tasks before loading a webview url in ios?

so I can't seem to figure out a way to perform some tasks before loading a url in ios webview.
Like in android we can use shouldOverrideUrlLoading.
#Override
public boolean shouldOverrideUrlLoading(final WebView view, String url) {
///sometask
}
but I can't find a function that does something like this in ios.
Say a user clicked on a href tag inside the webview that takes it to different page, how do I know what link that tag takes the user to? before changing the page.
Basically I want to check what url the webpage is about to load and perform extra steps according to that url.
You can use WKNavigationDelegate
set navigationDelegate in viewDidLoad
self.webView?.navigationDelegate = self
Observe URL in below navigationDelegate Method.
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
DispatchQueue.main.async {
if let urlString = navigationAction.request.url?.absoluteString{
print(urlString)
}
}
decisionHandler(.allow)
}

Tracking navigation in WKWebView doesn't work for button click

I am trying to catch a button click in my web view. Button "action" if I can say so, triggers certain url... I am not really able to catch this moment through WKNavigationDelegate. webview is set like this:
lazy var advertWebKitView: WKWebView = {
let webConfiguration = WKWebViewConfiguration()
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
webConfiguration.preferences = preferences
let webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.navigationDelegate = self
webView.uiDelegate = self
webView.translatesAutoresizingMaskIntoConstraints = false
return webView
}()
I am trying to catch that button click like this:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
decisionHandler(.allow)
guard let urlAsString = navigationAction.request.url?.absoluteString.lowercased() else {
return
}
if urlAsString.range(of: "...") != nil {
}
}
but this method doesn't trigger when button is clicked.
On android, it works and its done like this:
#Override
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
if (url.equals("...")) {
finish();
}
//...
super.doUpdateVisitedHistory(view, url, isReload);
}
what would be equal method and a way to track navigation correctly through wkwebview?
EDIT:
if there is some info needed about the webpage structure or so, please ask for details and I will add that too. I just didn't know which info may be relevant or helpful.
The button html looks like this:
<a class="lc-button lc-button--light" href="/close">OK</a>
Also one interesting thing is, that I have tried to use my code on other sites and navigation delegate methods were triggered correctly. Which leads me that problem is with our site and its html code or something about how the page is loaded after button is clicked, maybe...
I am using this myself and it works, it is slightly different than what you are trying until now but the right equivalent to the Android method.
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let requestURL = navigationAction.request.url, requestURL.pathComponents.contains("...") {
//Do your things
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
Use RedirectTo instead of LinkTo in react router for redirecting to /close
First set delegate in viewDidLoad,
self.webView.navigationDelegate = self
You can catch where your button is navigating you to by implementing
WKNavigationDelegate function, didFinish navigation
extension YourViewController: WKNavigationDelegate{
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
if let currentURL = webView.url{
if self.urlString == currentURL.absoluteString{
print(self.urlString)
}
}
}
}

Problem with navigation in WKWebView Swift

I am using WKWebView for my WebViewController in my app and I spot one problem with one URL
"http://www.viator.com/tours/San-Francisco/San-Francisco-Vista-Grande-Helicopter-Tour/d651-3538VISTAGRANDE?eap=brand-subbrand-75523&aid=vba75523en" there is no navigation through the web site when I tap some other event page nothing happens. Does anyone have any ideas about how to deal with that?
If you're talking about links opening in a new window, indeed, they don't create a new navigation action. But you can still intercept them, and load the request manually.
If your webview does not have a custom uiDelegate already, assign it to your view controller or something else, and then do something like this:
extension SomeViewController: WKUIDelegate {
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
// Intercept target=_blank links
guard
navigationAction.targetFrame == nil,
let url = navigationAction.request.url else {
return nil
}
let request = URLRequest(url: url)
webView.load(request)
return nil
}
}

WKWebview navigation response method isn't getting called upon clicking done button in a webview

I'm using WKWebview for my Swift app. For some reason, WKWebview navigation response delegate method isn't getting called when the user clicks the done button in a WKWebView. When I try the same action on the web, it redirects to the correct url.
So far I have tried clearing WKWebview cache before configuring WKWebView in viewDidLoad or if I get a memory warning. Also, ensured my info.plist allows arbitrary loads and NSExceptionAllowsInsecureHTTPLoads for testing purposes. Despite trying these things to debug, the navigation response delegate method still didn't get called.
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: #escaping (WKNavigationResponsePolicy) -> Void) {
if let webviewUrl = webView.url {
let urlString = webviewUrl.absoluteString
if urlString.contains("\(myUrlComponents)") {
self.navigationController?.popViewController(animated: true)
decisionHandler(.cancel)
return
}
}
decisionHandler(.allow)
}
I want to be able to detect a specific WKWebview url when the navigation response method gets called and go back to the previous screen.
I think you'll find that it will be called if you use the delegate function that looks identical to the one you're using except that the "decisionHandler" is a function taking WKNavigationActionPolicy as its argument.

using KVO to observe WKWebView's URL property not work in iOS 10

I use WKWebView to load my webpage. When user click a button in webpage, my webpage will open a custom schema URL (e.g. asfle://download?media_id=1). And
I use KVO to observe WKWebView's URL property to get the URL. It works well in iOS 9, but it doesn't work in iOS 10. I can't get the url. I use Xcode 8, swift 2.3.
override func viewDidLoad() {
super.viewDidLoad()
webView.addObserver(self, forKeyPath: "URL", options: .New, context: nil)
}
override func observeValueForKeyPath(keyPath: String?,
ofObject object: AnyObject?,
change: [String : AnyObject]?,
context: UnsafeMutablePointer<Void>)
{
print("url:\(webView.URL)")
}
In iOS 9, it can print url. But in iOS 10 only the website's url get printed, when user touch the button in webpage, nothing get printed.
You can still observe WKWebView's url property, just be sure to hold a strong reference to the returned observation:
class ViewController: UIViewController {
var webView: WKWebView!
var urlObservation: NSKeyValueObservation?
override func viewDidLoad() {
super.viewDidLoad()
// ...Initialize web view.
webView = WKWebView(frame: view.bounds, configuration: WKWebViewConfiguration())
webView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
view.addSubview(webView)
// Add observation.
urlObservation = webView.observe(\.url, changeHandler: { (webView, change) in
NSLog("Web view URL changed to \(webView.url?.absoluteString ?? "Empty")")
})
}
}
Also, don't forget to capture self as weak in observation closure if you plan to use it there, as otherwise you will get a retain cycle.
I found the solution. Instead of using KVO, use delegate to detect opening URL.
override func viewDidLoad() {
super.viewDidLoad()
webView.navigationDelegate = self
}
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
print("url:\(navigationAction.request.URL)")
decisionHandler(.Allow)
}
I found the optional solution.I have get same problem and change this line
webview.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options:.new, context: nil)

Resources