WKWebView won't open external links - ios

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.

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

Url stop loading inbetween on UIWebView and WKWebView. On iOS 11 only

My URL
https://app.educationgalaxy.com/games/ipadgame.html?retries=3&name=blastoff&rocket=3&level=2&score=0&gameTimer=150
My Findings
The URL works perfectly with all safari and other browsers.
(including iOS 11 mobile safari)
The URL works with all iOS versions other than iOS 11.
I tried to open the URL on both UIWebView and WKWebView but the same issue happens on both. i.e URL stops loading near completion and
nothing happens then.
Here is what I tried so far
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
// Objects
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
setupWebView()
loadUrl()
}
func setupWebView() {
let prefs = WKPreferences()
prefs.javaScriptEnabled = true
let config = WKWebViewConfiguration()
config.preferences = prefs
webView = WKWebView(frame: view.frame, configuration: config)
webView.navigationDelegate = self
view.addSubview(webView)
}
func loadUrl() {
webView.navigationDelegate = self
let url = URL(string: "https://app.educationgalaxy.com/games/ipadgame.html?retries=3&name=blastoff&rocket=3&level=2&score=0&gameTimer=150")!
var request = URLRequest(url: url)
request.cachePolicy = .reloadRevalidatingCacheData
webView.load(request)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print(webView.url!)
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
print(webView.url!)
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
print("error - \(error)")
}
}
I appreciate your help.
WkWebview is recently introduced and UIWebView is officially deprecated now.
Here is link on same issue posted on Apple forum.And Apple staff is currently figuring out the crash.
enter link description here
https://forums.developer.apple.com/thread/90411
Also try to add WKWebView.framework in your project.
And Transport Security.
Hope this information can help you.
I have analyse your code that is good and you have need check image formate.When I am using bellow code with image url link working fine .
import UIKit
import WebKit
class ViewController: UIViewController ,WKNavigationDelegate {
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
setupWebView()
loadUrl()
}
func setupWebView() {
let prefs = WKPreferences()
prefs.javaScriptEnabled = true
let config = WKWebViewConfiguration()
config.preferences = prefs
webView = WKWebView(frame: view.frame, configuration: config)
webView.navigationDelegate = self
view.addSubview(webView)
}
func loadUrl() {
webView.navigationDelegate = self
// let url = URL(string: "https://app.educationgalaxy.com/games/ipadgame.html?retries=3&name=blastoff&rocket=3&level=2&score=0&gameTimer=150")!
let url = URL(string: "https://upload.wikimedia.org/wikipedia/commons/2/2d/Snake_River_%285mb%29.jpg")!
var request = URLRequest(url: url)
request.cachePolicy = .reloadRevalidatingCacheData
webView.load(request)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print(webView.url!)
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
print(webView.url!)
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
print("error - \(error)")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// Add plist file
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>google.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
if WKWebView don't not support then declare .m file below code:
#import <WebKit/WebKit.h>
#interface WebScannerViewController()
{
WKWebView *webView;
}
#end
#implementation WebScannerViewController
- (void)viewDidLoad
{
[super viewDidLoad];
webView.hidden=YES;
webView.UIDelegate = self;
webView.navigationDelegate = self;
self.loadingSign.hidden = NO;
webView.frame=CGRectMake(0, 94, Width, Height-128);
webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
webView.customUserAgent = #"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/601.6.17 (KHTML, like Gecko) Version/9.1.1 Safari/601.6.17";
webView.navigationDelegate = self;
NSURL *url = [NSURL URLWithString:#"https://app.educationgalaxy.com/games/ipadgame.html?retries=3&name=blastoff&rocket=3&level=2&score=0&gameTimer=150"];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
[webView loadRequest:urlRequest];
}

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

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