UIWebView open links in Safari - ios

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

Related

WKWebView how to find the request in the WKNavigationDelegate methods

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

Xcode, Swift; Detect hyperlink click in UIWebView

I made an iOS app with Xcode and Swift.
I want to open specific hyperlinks in Safari browser, there others in the WebView itself.
To reach this, I'll have to check when an hyperlinks gets clicked.
This is the code I have so far:
//
// PushViewController.swift
// App
//
//
import UIKit
class PushViewController: UIViewController, UIWebViewDelegate {
#IBOutlet var openpushmessage: UIWebView!
var weburl:String = "http://www.example.com"
override func viewDidLoad() {
super.viewDidLoad()
let url: NSURL = NSURL(string: weburl)!
let requestURL: NSURLRequest = NSURLRequest(URL: url)
openpushmessage.loadRequest(requestURL)
}
override func webView(_ webView: UIWebView, shouldStartLoadWithRequest request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if navigationType == .linkClicked
{
print("You clicked a hypelink!")
}
return true
}
}
The code in viewDidLoad() opens the loads the main URL (http://www.example.com) from my server. That works fine.
override fun webView[…] should detect if a hyperlink is clicked and then print("You clicked a hypelink!").
But unfortunately I'm getting the following errors:
What am I doing wrong? How to get rid of these errors?
please try
for swift 3.0
public func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool
{
if navigationType == .linkClicked
{
}
return true;
}
swift 2.2
internal func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool
{
if navigationType == .LinkClicked
{
}
return true;
}
This is the working solution (Xcode 7.3.1 and Swift 2.2):
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if request.URL?.query?.rangeOfString("target=external") != nil {
if let url = request.URL {
UIApplication.sharedApplication().openURL(url)
return false
}
}
return true
}
Many thanks to danhhgl from freelancer.com!
I think you can solve with this code:
function reportBackToObjectiveC(string)
{
var iframe = document.createElement("iframe");
iframe.setAttribute("src", "callback://" + string);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
}
var links = document.getElementsByTagName("a");
for (var i=0; i<links.length; i++) {
links[i].addEventListener("click", function() {
reportBackToObjectiveC("link-clicked");
}, true);
}
in ios code:
if ([[[request URL] scheme] isEqualToString:#"callback"]) {
[self setNavigationLeavingCurrentPage:YES];
return NO;
}
More details you can check from here:

How can I force any links clicked within a WebView to open in Safari?

I have a WebView in my application and I would like any links clicked within the WebView to open in Safari (instead of the WebView itself).
I am developing the application in Swift.
What is the best method to do this?
This is done essentially the same way in Swift as in Obj-C:
First, declare that your view controller conforms to UIWebViewDelegate
class MyViewController: UIWebViewDelegate
Then implement webViewShouldStartLoadingWithRequest:navigationType: in your View Controller:
// Swift 1 & 2
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
switch navigationType {
case .LinkClicked:
// Open links in Safari
UIApplication.sharedApplication().openURL(request.URL)
return false
default:
// Handle other navigation types...
return true
}
}
// Swift 3
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
switch navigationType {
case .linkClicked:
// Open links in Safari
guard let url = request.url else { return true }
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
// openURL(_:) is deprecated in iOS 10+.
UIApplication.shared.openURL(url)
}
return false
default:
// Handle other navigation types...
return true
}
}
Finally, set your UIWebView's delegate, e.g., in viewDidLoad or in your Storyboard:
webView.delegate = self
Updated for swift 3
func webView(_: UIWebView, shouldStartLoadWith: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if navigationType == UIWebViewNavigationType.linkClicked {
UIApplication.shared.open(shouldStartLoadWith.url!, options: [:], completionHandler: nil)
return false
}
return true
}
According to recommendations in apple docs
Also UIWebView is deprecated after 12 iOS
Here is solution using Swift 5.3 and WKWebiew
First replace your UIWebview with WKWebview
Then conform to WKNavigationDelegate and implement method
Here is code
extension WebViewControllerImpl: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
guard case .linkActivated = navigationAction.navigationType,
let url = navigationAction.request.url
else {
decisionHandler(.allow)
return
}
decisionHandler(.cancel)
UIApplication.shared.openURL(url)
}
}
Then set 'navigationDelegate' for your wkWebView
wkWebView.navigationDelegate = self
You need to implement the method webViewShouldStartLoadingWithRequest:navigationType on your web view's delegate and look for the links you want to open in Safari. If you send those off to the OS with [[UIApplication sharedApplication]openURL:] they will open in Safari.
Updated for swift 4.2
func webView(_: UIWebView, shouldStartLoadWith: URLRequest, navigationType: UIWebView.NavigationType) -> Bool {
if navigationType == UIWebView.NavigationType.linkClicked {
UIApplication.shared.open(shouldStartLoadWith.url!, options: [:], completionHandler: nil)
return false
}
return true
}

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?

Resources