WKWebView how to find the request in the WKNavigationDelegate methods - ios

I'm trying to migrate from a UIWebView to WKWebView. shouldStartLoadWithRequest delegate method I could find the request as below:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSLog(#"request: %#", request);
if ([request.URL.host isEqualToString:#"jsbridge"])
{
NSString* requestType = [request.URL.pathComponents objectAtIndex:2];
NSString* key = [request.URL.pathComponents objectAtIndex:3];
NSLog(#"requestType: %# - key: %#", requestType, key);
if([requestType isEqualToString:#"audio"])
{
[self playAudio:key];
}
return YES;
}
I need help finding the same request in the WKNavigationDelegate methods. Do I use the didCommit or didFinishNavigation methods and if so how do I find the request that it came from? Could someone give me an example? Wish I could do this is Swift but I have to stick to Objective-C. Thanks in advance.

As I know the equivalent of webView:shouldStartLoadWithRequest:navigationType: in UIWebView is webView:decidePolicyForNavigationAction:decisionHandler: in WKWebView.
Inside webView:decidePolicyForNavigationAction:decisionHandler:, you can get the request from navigationAction.
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURLRequest *request = navigationAction.request;
// Do whatever you want with the request
decisionHandler(WKNavigationActionPolicyAllow);
}

Equivalent for UIWebview to WKWebview is as follows:
didFailLoadWithError => didFailNavigation
webViewDidFinishLoad => didFinishNavigation
webViewDidStartLoad => didStartProvisionalNavigation
shouldStartLoadWithRequest => decidePolicyForNavigationAction
The shouldStartWithRequest delegate method in WKWebview (Swift version) is as follows:
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: ((WKNavigationActionPolicy) -> Void)) {
if (webView.url?.host ?? "") == "jsbridge" {
let requestType = webView.url?.pathComponents[2]
let key = webView.url?.pathComponents[3]
print("requestType: \(requestType ?? "") - key: \(key ?? "")")
if (requestType == "audio") {
playAudio(key)
}
}
}

I created the custom class
class PKWebKitView: WKWebView,WKUIDelegate,WKNavigationDelegate {
var webView :WKWebView!
func loadRequestUrl(myRequest : URLRequest) {
self.navigationDelegate = self
self.uiDelegate = self
self.load(myRequest)
} }
extension PKWebKitView: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let host = navigationAction.request.url?.absoluteString.removingPercentEncoding {
if host.contains(<endpoint check>) {
if let delegateWrapper = wkWebviewDelegate {
decisionHandler(.cancel)
}
}
if host.contains(<endpoint check>) {
let responseString = host
let query = "<Your query>"
wkWebviewDelegate?.launchScreen(query)
}
}
To Use:
let webConfiguration = WKWebViewConfiguration()
webView = PKWebKitView(frame: webViewContainer.frame, configuration: webConfiguration)
webView.loadRequestUrl(myRequest

Related

Migrating from UIWebView to WKWebView

I'm migrating from UIWebView to WKWebView, how can I rewrite these function for WKWebView?
func webViewDidStartLoad(webView: UIWebView){}
func webViewDidFinishLoad(webView: UIWebView){}
and
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
print("webview asking for permission to start loading")
if navigationType == .LinkActivated && !(request.URL?.absoluteString.hasPrefix("http://www.myWebSite.com/exemlpe"))!{
UIApplication.sharedApplication().openURL(request.URL!)
print(request.URL?.absoluteString)
return false
}
print(request.URL?.absoluteString)
lastUrl = (request.URL?.absoluteString)!
return true
}
func webView(webView: UIWebView, didFailLoadWithError error: NSError?) {
print("webview did fail load with error: \(error)")
let testHTML = NSBundle.mainBundle().pathForResource("back-error-bottom", ofType: "jpg")
let baseUrl = NSURL(fileURLWithPath: testHTML!)
let htmlString:String! = "myErrorinHTML"
self.webView.loadHTMLString(htmlString, baseURL: baseUrl)
}
UIWebView => WKWebView Equivalent
UIWebViewDelegate => WKNavigationDelegate
delegate => navigationDelegate
didFailLoadWithError => didFailNavigation
webViewDidFinishLoad => didFinishNavigation
webViewDidStartLoad => didStartProvisionalNavigation
shouldStartLoadWithRequest => decidePolicyForNavigationAction
About shouldStartLoadWithRequest you can write:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
print("webView:\(webView) decidePolicyForNavigationAction:\(navigationAction) decisionHandler:\(decisionHandler)")
switch navigationAction.navigationType {
case .linkActivated:
if navigationAction.targetFrame == nil {
self.webView?.loadRequest(navigationAction.request)
}
if let url = navigationAction.request.url, !url.absoluteString.hasPrefix("http://www.myWebSite.com/example") {
UIApplication.shared.open(url)
print(url.absoluteString)
decisionHandler(.cancel)
return
}
default:
break
}
if let url = navigationAction.request.url {
print(url.absoluteString)
}
decisionHandler(.allow)
}
And for the didFailLoadWithError:
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
print("webView:\(webView) didFailNavigation:\(navigation) withError:\(error)")
let testHTML = Bundle.main.path(forResource: "back-error-bottom", ofType: "jpg")
let baseUrl = URL(fileURLWithPath: testHTML!)
let htmlString = "myErrorInHTML"
self.webView.loadHTMLString(htmlString, baseURL: baseUrl)
}
Here is the Objective-C methods for the migration:
shouldStartLoadWithRequest -> decidePolicyForNavigationAction
Remember to call the decisionHandler:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if (navigationAction.navigationType == WKNavigationTypeLinkActivated) {
}
NSString *url = [navigationAction.request.URL query];
decisionHandler(WKNavigationActionPolicyAllow);
}
webViewDidStartLoad -> didStartProvisionalNavigation
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
}
webViewDidFinishLoad -> didFinishNavigation
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
}
didFailLoadWithError -> didFailNavigation
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
}
Migrating UIWebView to WKWebView, Swift 4:
Equivalent of shouldStartLoadWithRequest:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
var action: WKNavigationActionPolicy?
defer {
decisionHandler(action ?? .allow)
}
guard let url = navigationAction.request.url else { return }
print(url)
if navigationAction.navigationType == .linkActivated, url.absoluteString.hasPrefix("http://www.example.com/open-in-safari") {
action = .cancel // Stop in WebView
UIApplication.shared.openURL(url) // Open in Safari
}
}
Equivalent of webViewDidStartLoad:
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
print(String(describing: webView.url))
}
Equivalent of didFailLoadWithError:
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
let nserror = error as NSError
if nserror.code != NSURLErrorCancelled {
webView.loadHTMLString("404 - Page Not Found", baseURL: URL(string: "http://www.example.com/"))
}
}
Equivalent of webViewDidFinishLoad:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print(String(describing: webView.url))
}

WKWebView won't open external links

So I was in the process of converting my UIWebView app over to WKWebView only to find out that it won't open external websites, i.e. dropbox, facebook etc.
It loads my site in the viewDidLoad, so that's not an issue.
example:
NSURL *nsurl=[NSURL URLWithString:#"example.com"];
NSURLRequest *nsrequest=[NSURLRequest requestWithURL:nsurl];
[webView loadRequest:nsrequest];
webView.navigationDelegate = self;
webView.UIDelegate = self;
[self.view addSubview:webView];
I have called:
-(void)webView:(WKWebView *)webView didStartProvisionalNavigation: (WKNavigation *)navigation {}
- (void)webView:(WKWebView *)webView didFinishNavigation: (WKNavigation *)navigation{}
along with:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {}
decisionHandler(WKNavigationActionPolicyAllow);
and finally in my info.plist I added:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSThirdPartyExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
Yet nothing works. What am I doing wrong?
Any help would as always be greatly appreciated.
Ok So I managed to get it, along with some help from some forgotten sites (sorry) I managed to get it all working.
So below is pretty much everything I'm using in my webview.
//Webview Start/Finish Request
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSLog(#"didStartProvisionalNavigation: %#", navigation);
}
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
NSLog(#"didReceiveServerRedirectForProvisionalNavigation: %#", navigation);
}
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
NSLog(#"didFailProvisionalNavigation: %#navigation, error: %#", navigation, error);
}
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
NSLog(#"didCommitNavigation: %#", navigation);
}
- (void)webView:(WKWebView *)webView didFinishLoadingNavigation:(WKNavigation *)navigation {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSLog(#"didFinishLoadingNavigation: %#", navigation);
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
NSLog(#"didFailNavigation: %#, error %#", navigation, error);
}
- (void)_webViewWebProcessDidCrash:(WKWebView *)webView {
NSLog(#"WebContent process crashed; reloading");
}
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
if (!navigationAction.targetFrame.isMainFrame) {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[self.webView loadRequest:navigationAction.request];
}
return nil;
}
- (void)webView:(WKWebView *)webView didFinishNavigation: (WKNavigation *)navigation{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSLog(#"didFinish: %#; stillLoading:%#", [self.webView URL], (self.webView.loading?#"NO":#"YES"));
}
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
NSLog(#"decidePolicyForNavigationResponse");
decisionHandler(WKNavigationResponsePolicyAllow);
}
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if (decisionHandler) {
decisionHandler(WKNavigationActionPolicyAllow);
}
}
So I help all of this helps somebody.
Here is the swift 3.0 version of this answer
Add these two delegates
// webView is WKWebview object
webView.navigationDelegate = self;
webView.uiDelegate = self;
override these delegate callback methods
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
print("didStartProvisionalNavigation: \(navigation)")
}
func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation) {
print("didReceiveServerRedirectForProvisionalNavigation: \(navigation)")
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation, withError error: Error) {
print("didFailProvisionalNavigation: \(navigation), error: \(error)")
}
func webView(_ webView: WKWebView, didFinishLoading navigation: WKNavigation) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
print("didFinishLoadingNavigation: \(navigation)")
}
func _webViewWebProcessDidCrash(_ webView: WKWebView) {
print("WebContent process crashed; reloading")
}
func webView(_ webView: WKWebView,createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView?{
//if <a> tag does not contain attribute or has _blank then navigationAction.targetFrame will return nil
if let trgFrm = navigationAction.targetFrame {
if(!trgFrm.isMainFrame){
UIApplication.shared.isNetworkActivityIndicatorVisible = true
self.webView.load(navigationAction.request)
}
}
return nil
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!){
UIApplication.shared.isNetworkActivityIndicatorVisible = false
print("didFinish: \(String(describing: self.webView.url)); stillLoading:\(self.webView.isLoading ? "NO" : "YES")")
}
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: #escaping (_: WKNavigationResponsePolicy) -> Void) {
print("decidePolicyForNavigationResponse")
decisionHandler(.allow)
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (_: WKNavigationActionPolicy) -> Void) {
decisionHandler(.allow)
}
Pease check the return type of this method,
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
if (!navigationAction.targetFrame.isMainFrame) {
[self.webView loadRequest:navigationAction.request];
}
return self.webView;
}
As it is WKWebView, if you return your webview, it will load the URL for you and give you the desired page to be displayed.

Launching phone/email/map links in WKWebView

KINWebBrowser is an open source web browser module for iOS apps. I recently upgraded KINWebBrowser to use WKWebView to begin phasing out UIWebView. This yields significant improvements, but:
Problem: WKWebView does not enable users to launch links containing URLs for phone numbers, email address, maps, etc.
How can I configure a WKWebView to launch the standard iOS behaviors for these alternate URLs when launched as links from the displayed page?
All of the code is available here
More info on WKWebKit
See the issue on the KINWebBrowser GitHub here
I was able to get it to work for the Google Maps link (which appears to be related to the target="_blank") and for the tel: scheme by adding this function to your KINWebBrowserViewController.m
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
if(webView != self.wkWebView) {
decisionHandler(WKNavigationActionPolicyAllow);
return;
}
UIApplication *app = [UIApplication sharedApplication];
NSURL *url = navigationAction.request.URL;
if (!navigationAction.targetFrame) {
if ([app canOpenURL:url]) {
[app openURL:url];
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
}
if ([url.scheme isEqualToString:#"tel"])
{
if ([app canOpenURL:url])
{
[app openURL:url];
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
}
decisionHandler(WKNavigationActionPolicyAllow);
}
Works on xcode 8.1, Swift 2.3.
For target="_blank", phone number (tel:) and email (mailto:) links.
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
if webView != self.webview {
decisionHandler(.Allow)
return
}
let app = UIApplication.sharedApplication()
if let url = navigationAction.request.URL {
// Handle target="_blank"
if navigationAction.targetFrame == nil {
if app.canOpenURL(url) {
app.openURL(url)
decisionHandler(.Cancel)
return
}
}
// Handle phone and email links
if url.scheme == "tel" || url.scheme == "mailto" {
if app.canOpenURL(url) {
app.openURL(url)
decisionHandler(.Cancel)
return
}
}
decisionHandler(.Allow)
}
}
Updated for swift 4.0
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if webView != self.webView {
decisionHandler(.allow)
return
}
let app = UIApplication.shared
if let url = navigationAction.request.url {
// Handle target="_blank"
if navigationAction.targetFrame == nil {
if app.canOpenURL(url) {
app.open(url)
decisionHandler(.cancel)
return
}
}
// Handle phone and email links
if url.scheme == "tel" || url.scheme == "mailto" {
if app.canOpenURL(url) {
app.open(url)
}
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
}
You need to implemented an other callback to get this right (Swift 5.0):
// Gets called if webView cant handle URL
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
guard let failingUrlStr = (error as NSError).userInfo["NSErrorFailingURLStringKey"] as? String else { return }
let failingUrl = URL(string: failingUrlStr)!
switch failingUrl {
// Needed to open Facebook
case _ where failingUrlStr.hasPrefix("fb:"):
if #available(iOS 10.0, *) {
UIApplication.shared.open(failingUrl, options: [:], completionHandler: nil)
return
} // Else: Do nothing, iOS 9 and earlier will handle this
// Needed to open Mail-app
case _ where failingUrlStr.hasPrefix("mailto:"):
if UIApplication.shared.canOpenURL(failingUrl) {
UIApplication.shared.open(failingUrl, options: [:], completionHandler: nil)
return
}
// Needed to open Appstore-App
case _ where failingUrlStr.hasPrefix("itmss://itunes.apple.com/"):
if UIApplication.shared.canOpenURL(failingUrl) {
UIApplication.shared.open(failingUrl, options: [:], completionHandler: nil)
return
}
default: break
}
}
Now Facebook, Mail, Appstore,.. getting called directly from your app without the need to open Safari
Edit: replaced custom startsWith() method with standard hasPrefix() method.
This helps me for Xcode 8 WKWebview
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame == nil {
let url = navigationAction.request.url
if url?.description.range(of: "http://") != nil || url?.description.range(of: "https://") != nil || url?.description.range(of: "mailto:") != nil || url?.description.range(of: "tel:") != nil {
UIApplication.shared.openURL(url!)
}
}
return nil
}
EDITED:
In link must be attribute target="_blank".
I'm landed here searching for how to open gmail attachments on wkwebview.
My solution is simple:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.targetFrame == nil, let redirect = navigationAction.request.url {
if UIApplication.shared.canOpenURL(redirect) {
self.webViewMail?.load(navigationAction.request)
decisionHandler(.cancel)
return
}
}
decisionHandler(.allow)
}
UPDATE FOR SWIFT 4.2
Sorry to dig up an old post, but I was having the same issues and have updated the solution for Swift 4.2. I have put my solution here so that it may help others and if not I will hopefully find it the next time I am working with WKWebView!
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
let url = navigationAction.request.url?.absoluteString
let urlElements = url?.components(separatedBy: ":") ?? []
switch urlElements[0] {
case "tel":
UIApplication.shared.openURL(navigationAction.request.url!)
decisionHandler(.cancel)
case "mailto":
UIApplication.shared.openURL(navigationAction.request.url!)
decisionHandler(.cancel)
default:
decisionHandler(.allow)
}
}
I used the following site as inspiration:
SubzDesignz iOS Swift 4 WKWebview – Detect tel, mailto, target=”_blank” and CheckConnection
Above answer workes for me, but I needed it to rewrite for swift 2.3
if navigationAction.targetFrame == nil {
let url = navigationAction.request.mainDocumentURL
if url?.description.rangeOfString("mailto:")?.startIndex != nil ||
url?.description.rangeOfString("tel:")?.startIndex != nil
{
if #available(iOS 10, *) {
UIApplication.sharedApplication().openURL(url!,options: [:], completionHandler: nil)
} else {
UIApplication.sharedApplication().openURL(url!) // deprecated
}
}
}

Why is WKWebView not opening links with target="_blank"?

WKWebView does not open any links which have target="_blank" a.k.a. 'Open in new Window' attribute in their HTML <a href>-Tag.
My solution is to cancel the navigation and load the request with loadRequest: again. This will be come the similar behavior like UIWebView which always open new window in the current frame.
Implement the WKUIDelegate delegate and set it to _webview.uiDelegate. Then implement:
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
if (!navigationAction.targetFrame.isMainFrame) {
[webView loadRequest:navigationAction.request];
}
return nil;
}
The answer from #Cloud Xu is the correct answer. Just for reference, here it is in Swift:
// this handles target=_blank links by opening them in the same view
func webView(webView: WKWebView!, createWebViewWithConfiguration configuration: WKWebViewConfiguration!, forNavigationAction navigationAction: WKNavigationAction!, windowFeatures: WKWindowFeatures!) -> WKWebView! {
if navigationAction.targetFrame == nil {
webView.loadRequest(navigationAction.request)
}
return nil
}
To use latest version of Swift 4.2+
import WebKit
Extend your class with WKUIDelegate
Set delegate for webview
self.webView.uiDelegate = self
Implement protocol method
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame == nil {
webView.load(navigationAction.request)
}
return nil
}
Add yourself as the WKNavigationDelegate
_webView.navigationDelegate = self;
and implement following code in the delegate callback decidePolicyForNavigationAction:decisionHandler:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
//this is a 'new window action' (aka target="_blank") > open this URL externally. If we´re doing nothing here, WKWebView will also just do nothing. Maybe this will change in a later stage of the iOS 8 Beta
if (!navigationAction.targetFrame) {
NSURL *url = navigationAction.request.URL;
UIApplication *app = [UIApplication sharedApplication];
if ([app canOpenURL:url]) {
[app openURL:url];
}
}
decisionHandler(WKNavigationActionPolicyAllow);
}
P.S.: This code is from my little project STKWebKitViewController, which wraps a usable UI around WKWebView.
If you've already set the WKWebView.navigationDelegate
WKWebView.navigationDelegate = self;
you just need to implement:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
BOOL shouldLoad = [self shouldStartLoadWithRequest:navigationAction.request]; // check the url if necessary
if (shouldLoad && navigationAction.targetFrame == nil) {
// WKWebView ignores links that open in new window
[webView loadRequest:navigationAction.request];
}
// always pass a policy to the decisionHandler
decisionHandler(shouldLoad ? WKNavigationActionPolicyAllow : WKNavigationActionPolicyCancel);
}
this way you don't need to implement the WKUIDelegate method.
Cloud xu's answer solves my issue.
In case someone need the equivalent Swift(4.x/5.0) version, here is it:
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if let frame = navigationAction.targetFrame,
frame.isMainFrame {
return nil
}
// for _blank target or non-mainFrame target
webView.load(navigationAction.request)
return nil
}
Of course you have to set webView.uiDelegate firstly.
None of those solutions worked for me, I did solve the issue by :
1) Implementing WKUIDelegate
#interface ViewController () <WKNavigationDelegate, WKUIDelegate>
2) Setting the UIDelegate delegate of the wkWebview
self.wkWebview.UIDelegate = self;
3) Implementing the createWebViewWithConfiguration method
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
if (!navigationAction.targetFrame.isMainFrame) {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[[UIApplication sharedApplication] openURL:[navigationAction.request URL]];
}
return nil; }
I confirm that Bill Weinman's Swift code is correct. But need to mention that you also need to delegate UIDelegate for it to work, in case you are new to iOS developing like me.
Something like this:
self.webView?.UIDelegate = self
So there's three places that you need to make changes.
You can also push another view controller, or open a new tab, etc:
func webView(webView: WKWebView, createWebViewWithConfiguration configuration: WKWebViewConfiguration, forNavigationAction navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
var wv: WKWebView?
if navigationAction.targetFrame == nil {
if let vc = self.storyboard?.instantiateViewControllerWithIdentifier("ViewController") as? ViewController {
vc.url = navigationAction.request.URL
vc.webConfig = configuration
wv = vc.view as? WKWebView
self.navigationController?.pushViewController(vc, animated: true)
}
}
return wv
}
Based on allen huang answer
Details
Xcode Version 10.3 (10G8), Swift 5
Targets
detect links with target=“_blank”
push view controller with webView if current controller has navigationController
present view controller with webView in all other cases
Solution
webView.uiDelegate = self
// .....
extension ViewController: WKUIDelegate {
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
guard navigationAction.targetFrame == nil,
let url = navigationAction.request.url else { return nil }
let vc = ViewController(url: url, configuration: configuration)
if let navigationController = navigationController {
navigationController.pushViewController(vc, animated: false)
return vc.webView
}
present(vc, animated: true, completion: nil)
return nil
}
}
Full sample
Info.plist
add in your Info.plist transport security setting
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
ViewController
import UIKit
import WebKit
class ViewController: UIViewController {
private lazy var url = URL(string: "https://www.w3schools.com/html/tryit.asp?filename=tryhtml_links_target")!
private weak var webView: WKWebView!
init (url: URL, configuration: WKWebViewConfiguration) {
super.init(nibName: nil, bundle: nil)
self.url = url
navigationItem.title = ""
}
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
override func viewDidLoad() {
super.viewDidLoad()
initWebView()
webView.loadPage(address: url)
}
private func initWebView() {
let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration())
view.addSubview(webView)
self.webView = webView
webView.navigationDelegate = self
webView.uiDelegate = self
webView.translatesAutoresizingMaskIntoConstraints = false
webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
webView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
webView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
webView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
}
}
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
guard let host = webView.url?.host else { return }
navigationItem.title = host
}
}
extension ViewController: WKUIDelegate {
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
guard navigationAction.targetFrame == nil,
let url = navigationAction.request.url else { return nil }
let vc = ViewController(url: url, configuration: configuration)
if let navigationController = navigationController {
navigationController.pushViewController(vc, animated: false)
return vc.webView
}
present(vc, animated: true, completion: nil)
return nil
}
}
extension WKWebView {
func loadPage(address url: URL) { load(URLRequest(url: url)) }
func loadPage(address urlString: String) {
guard let url = URL(string: urlString) else { return }
loadPage(address: url)
}
}
Storyboards
Version 1
Version 2
To open _blank pages in Mobile Safari and if you use Swift:
webView.navigationDelegate = self
And implement this in the WKNavigationDelegate:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.targetFrame == nil, let url = navigationAction.request.url {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
decisionHandler(WKNavigationActionPolicy.allow)
}
This worked for me:
-(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
if (!navigationAction.targetFrame.isMainFrame) {
WKWebView *newWebview = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
newWebview.UIDelegate = self;
newWebview.navigationDelegate = self;
[newWebview loadRequest:navigationAction.request];
self.view = newWebview;
return newWebview;
}
return nil;
}
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
decisionHandler(WKNavigationActionPolicyAllow);
}
- (void)webViewDidClose:(WKWebView *)webView {
self.view = self.webView;
}
As you can see, what we do here is just opening a new webViewwith the new url and controlling the posibility of being closed, just if you need a response from that second webviewto be displayed on the first.
I encountered some issues that can not be resolved by just using webView.load(navigationAction.request). So I use create a new webView to do and it just works fine.
//MARK:- WKUIDelegate
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
NSLog(#function)
if navigationAction.targetFrame == nil {
NSLog("=> Create a new webView")
let webView = WKWebView(frame: self.view.bounds, configuration: configuration)
webView.uiDelegate = self
webView.navigationDelegate = self
self.webView = webView
return webView
}
return nil
}
**Use following function to create web view**
func initWebView(configuration: WKWebViewConfiguration)
{
let webView = WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
webView.uiDelegate = self
webView.navigationDelegate = self
view.addSubview(webView)
self.webView = webView
}
**In View Did Load:**
if webView == nil { initWebView(configuration: WKWebViewConfiguration()) }
webView?.load(url: url1)
**WKUIDelegate Method need to be implemented**
extension WebViewController: WKUIDelegate {
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
// push new screen to the navigation controller when need to open url in another "tab"
print("url:\(String(describing: navigationAction.request.url?.absoluteString))")
if let url = navigationAction.request.url, navigationAction.targetFrame == nil {
let viewController = WebViewController()
viewController.initWebView(configuration: configuration)
viewController.url1 = url
DispatchQueue.main.async { [weak self] in
self?.navigationController?.pushViewController(viewController, animated: true)
}
return viewController.webView
}
return nil
}
}
extension WKWebView
{
func load(url: URL) { load(URLRequest(url: url)) }
}
Use this method to download pdf in web view
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView?

UIWebView open links in Safari

I have a very simple UIWebView with content from my application bundle. I would like any links in the web view to open in Safari instead of in the web view. Is this possible?
Add this to the UIWebView delegate:
(edited to check for navigation type. you could also pass through file:// requests which would be relative links)
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (navigationType == UIWebViewNavigationTypeLinkClicked ) {
[[UIApplication sharedApplication] openURL:[request URL]];
return NO;
}
return YES;
}
Swift Version:
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if navigationType == UIWebViewNavigationType.LinkClicked {
UIApplication.sharedApplication().openURL(request.URL!)
return false
}
return true
}
Swift 3 version:
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if navigationType == UIWebViewNavigationType.linkClicked {
UIApplication.shared.openURL(request.url!)
return false
}
return true
}
Swift 4 version:
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebView.NavigationType) -> Bool {
guard let url = request.url, navigationType == .linkClicked else { return true }
UIApplication.shared.open(url, options: [:], completionHandler: nil)
return false
}
Update
As openURL has been deprecated in iOS 10:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (navigationType == UIWebViewNavigationTypeLinkClicked ) {
UIApplication *application = [UIApplication sharedApplication];
[application openURL:[request URL] options:#{} completionHandler:nil];
return NO;
}
return YES;
}
If anyone wonders, Drawnonward's solution would look like this in Swift:
func webView(webView: UIWebView!, shouldStartLoadWithRequest request: NSURLRequest!, navigationType: UIWebViewNavigationType) -> Bool {
if navigationType == UIWebViewNavigationType.LinkClicked {
UIApplication.sharedApplication().openURL(request.URL)
return false
}
return true
}
One quick comment to user306253's answer: caution with this, when you try to load something in the UIWebView yourself (i.e. even from the code), this method will prevent it to happened.
What you can do to prevent this (thanks Wade) is:
if (inType == UIWebViewNavigationTypeLinkClicked) {
[[UIApplication sharedApplication] openURL:[inRequest URL]];
return NO;
}
return YES;
You might also want to handle the UIWebViewNavigationTypeFormSubmitted and UIWebViewNavigationTypeFormResubmitted types.
The other answers have one problem: they rely on the action you do and not on the link itself to decide whether to load it in Safari or in webview.
Now sometimes this is exactly what you want, which is fine; but some other times, especially if you have anchor links in your page, you want really to open only external links in Safari, and not internal ones. In that case you should check the URL.host property of your request.
I use that piece of code to check whether I have a hostname in the URL that is being parsed, or if it is embedded html:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
static NSString *regexp = #"^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9])[.])+([A-Za-z]|[A-Za-z][A-Za-z0-9-]*[A-Za-z0-9])$";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", regexp];
if ([predicate evaluateWithObject:request.URL.host]) {
[[UIApplication sharedApplication] openURL:request.URL];
return NO;
} else {
return YES;
}
}
You can of course adapt the regular expression to fit your needs.
In Swift you can use the following code:
extension YourViewController: UIWebViewDelegate {
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebView.NavigationType) -> Bool {
if let url = request.url, navigationType == UIWebView.NavigationType.linkClicked {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
return false
}
return true
}
}
Make sure you check for the URL value and the navigationType.
App rejection Note:
Finally UIWbView is dead and Apple will not longer accept it.
Apple started sending email to all the App owner who are still using UIWebView:
Deprecated API Usage - Apple will stop accepting submissions of apps that use UIWebView APIs.
Apple takes User Privacy very seriously and it is obvious that they won’t allow insecure webview.
So do remove UIWebView from your app as soon as possible. don't use try to use UIWebView in new created app and I Prefer to using WKWebView if possible
ITMS-90809: Deprecated API Usage - Apple will stop accepting
submissions of apps that use UIWebView APIs . See
https://developer.apple.com/documentation/uikit/uiwebview for more
information.
Example:
import UIKit
import WebKit
class WebInfoController: UIViewController,WKNavigationDelegate {
var webView : WKWebView = {
var webview = WKWebView()
return webview
}()
var _fileName : String!
override func viewDidLoad() {
self.view.addSubview(webView)
webView.fillSuperview()
let url = Bundle.main.url(forResource: _fileName, withExtension: "html")!
webView.loadFileURL(url, allowingReadAccessTo: url)
let request = URLRequest(url: url)
webView.load(request)
}
func webView(webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: NSError) {
print(error.localizedDescription)
}
func webView(webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
print("Strat to load")
}
func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
print("finish to load")
}
}
Here's the Xamarin iOS equivalent of drawnonward's answer.
class WebviewDelegate : UIWebViewDelegate {
public override bool ShouldStartLoad (UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType) {
if (navigationType == UIWebViewNavigationType.LinkClicked) {
UIApplication.SharedApplication.OpenUrl (request.Url);
return false;
}
return true;
}
}
The accepted answer does not work.
If your page loads URLs via Javascript, the navigationType will be UIWebViewNavigationTypeOther. Which, unfortunately, also includes background page loads such as analytics.
To detect page navigation, you need to compare the [request URL] to the [request mainDocumentURL].
This solution will work in all cases:
- (BOOL)webView:(UIWebView *)view shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)type
{
if ([[request URL] isEqual:[request mainDocumentURL]])
{
[[UIApplication sharedApplication] openURL:[request URL]];
return NO;
}
else
{
return YES;
}
}
In my case I want to make sure that absolutely everything in the web view opens Safari except the initial load and so I use...
- (BOOL)webView:(UIWebView *)inWeb shouldStartLoadWithRequest:(NSURLRequest *)inRequest navigationType:(UIWebViewNavigationType)inType {
if(inType != UIWebViewNavigationTypeOther) {
[[UIApplication sharedApplication] openURL:[inRequest URL]];
return NO;
}
return YES;
}
UIWebView and UIWebViewDelegate are deprecated. You won't be allowed to push an update to the Appstore with it. Reference
Use WKWebView and WKNavigationDelegate
Sample code:
class YourClass: WKNavigationDelegate {
override public func viewDidLoad() {
super.viewDidLoad()
let webView = WKWebView()
webView.navigationDelegate = self
self.view.addaddSubview(webView)
}
public func webView(_ webView: WKWebView,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let cred = URLCredential(trust: challenge.protectionSpace.serverTrust!)
completionHandler(.useCredential, cred)
}
public func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: #escaping (WKNavigationActionPolicy) -> Swift.Void) {
self.webView.load(navigationAction.request)
decisionHandler(.allow)
}
}

Resources