Reputation: 705
Inside my iOS Application I have an UIWebView.
Now I want all links that have the attribute target="_blank" not to open inside my WebView but externally in Safari.
How can I do this?
Upvotes: 21
Views: 27735
Reputation: 11
Try this.
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:@"mailto"])
{
if ([app canOpenURL:url])
{
[app openURL:url];
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
}
decisionHandler(WKNavigationActionPolicyAllow);
Upvotes: 1
Reputation: 21
Just in case if some one is looking for answer in Swift4
For internal loads make sure you call the decisionHandler() closure with .cancel so the load halts, while also calling UIApplication.shared.open() to have the URL open in the external browser.
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url {
if url.host == "your url.com" {
UIApplication.shared.open(url)
decisionHandler(.cancel)
return
}
}
decisionHandler(.allow)
}
Upvotes: 2
Reputation: 139
I had the same question and unfortunately these answers drew me in a completely wrong and very complex way. Really the question is answered as simply as "you need to use WebViewPolicyDelegateProtocol".
In -viewDidLoad of the view controller implementation you write:
[myWebView setPolicyDelegate:self];
In your view controller class interface you must add two items:
- (void)webView:(WebView *)webView
decidePolicyForNavigationAction:(NSDictionary *)actionInformation
request:(NSURLRequest *)request
frame:(WebFrame *)frame
decisionListener:(id<WebPolicyDecisionListener>)listener;
- (void)webView:(WebView *)webView
decidePolicyForNewWindowAction:(NSDictionary *)actionInformation
request:(NSURLRequest *)request
newFrameName:(NSString *)frameName
decisionListener:(id<WebPolicyDecisionListener>)listener;
And implement them as easy as:
- (void)webView:(WebView *)webView
decidePolicyForNavigationAction:(NSDictionary *)actionInformation
request:(NSURLRequest *)request
frame:(WebFrame *)frame
decisionListener:(id<WebPolicyDecisionListener>)listener {
// just the default behavior, though you're free to add any url filtering you like...
[listener use];
}
- (void)webView:(WebView *)webView
decidePolicyForNewWindowAction:(NSDictionary *)actionInformation
request:(NSURLRequest *)request
newFrameName:(NSString *)frameName
decisionListener:(id<WebPolicyDecisionListener>)listener {
// frameName is your "target" parameter value
if([frameName isEqualToString:@"_blank"]) {
[[NSWorkSpace sharedWorkSpace] loadURL:[request URL]];
} else {
[listener use];
}
}
Also refer to the Apple docs
I've used this way in my project, where frameset is used in the root HTML, loaded into the WebView. All cross-links pointing to another existing frame don't cause the second message call, so only new (external) targets are processed here. It works OK for me.
Upvotes: 0
Reputation: 1741
I based my answer on the one from Benjamin Piette but needed to adjust the script since the links to be adjusted in my case were generated asynchronously by an other javascript.
NSString* const kOpenInNewTabPrefix = @"myOpenInNewTabPrefix:";//This NEEDS to end with ':'
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([[request.URL.absoluteString lowercaseString] hasPrefix:[kOpenInNewTabPrefix lowercaseString]])
{
// JS-hacked URl is a target=_blank url - manually open the browser.
NSURL *url = [NSURL URLWithString:[request.URL.absoluteString substringFromIndex:[kOpenInNewTabPrefix length]]];
[[UIApplication sharedApplication] openURL:url];
return YES;
}
return YES;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
//based on http://stackoverflow.com/questions/8490038/open-target-blank-links-outside-of-uiwebview-in-safari
// JS Injection hack to solve the target="_blank" issue and open a real browser in such case.
NSString *JSInjection = [NSString stringWithFormat:@"javascript: "
"document.getElementsByTagName('body')[0].addEventListener('click', function(e){"
" var a = e.target;"
" if(a.nodeName != 'A'){"
" return;"
" }"
" var target = a.target;"
" var href = a.href;"
" var prefix = '%@';"
" if(href.substring(0, %lu) != '%@' && target == '_blank'){"
" a.href = prefix + href;"
" }"
"})"
, [kOpenInNewTabPrefix lowercaseString]
, (unsigned long)[kOpenInNewTabPrefix length]
, [kOpenInNewTabPrefix lowercaseString]];
[webView stringByEvaluatingJavaScriptFromString:JSInjection];
}
Upvotes: 0
Reputation: 3783
My answer, which is a from an answer I found on stack overflow for the Android WebView. But actually, both webview have the same problem and same (dirty) fix:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([request.URL.absoluteString hasPrefix:@"newtab:"])
{
// JS-hacked URl is a target=_blank url - manually open the browser.
NSURL *url = [NSURL URLWithString:[request.URL.absoluteString substringFromIndex:7]];
[[UIApplication sharedApplication] openURL:url];
return true;
}
return true;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// JS Injection hack to solve the target="_blank" issue and open a real browser in such case.
NSString *JSInjection = @"javascript: var allLinks = document.getElementsByTagName('a'); if (allLinks) {var i;for (i=0; i<allLinks.length; i++) {var link = allLinks[i];var target = link.getAttribute('target'); if (target && target == '_blank') {link.setAttribute('target','_self');link.href = 'newtab:'+link.href;}}}";
[webView stringByEvaluatingJavaScriptFromString:JSInjection];
}
This solves both the target="_blank" issue to open in safari, AND keeps opening standard links within the webview.
Upvotes: 38
Reputation: 2884
Kudos to Martin Magakian! Here is the modification based on spankmaster79's suggestion:
- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest: (NSURLRequest *)request navigationType: (UIWebViewNavigationType)navigationType {
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
NSURL *url = [request URL];
NSString *param = [url query];
if ([param rangeOfString: @"openInSafari=true"].location != NSNotFound){
[[UIApplication sharedApplication] openURL: url];
return NO;
}
}
return YES;
}
Upvotes: 3
Reputation: 3766
The problem with wedo's solution is that all your links will open in Safari.
Two solutions:
1 - JavaScript callback to Objective-C when target="_blank"
To achieve your problem you need to add some javascript on all your links, check if they have the attribute _blank, then call back your objective-C code from JavaScript and run:
[[UIApplication sharedApplication] openURL:myUrl];
I personally don't like this solution because it's a lot of code, callback, complexity and a bit tricky...
2 - Checking url parameter
If you have access to the HTML code (note in both solution you need access to HTML) I recommend you remove the target="_blank" and add the parameter ?openInSafari=true
In the UIWebViewDelegate add the following code:
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
NSURL *url = [request URL];
NSDictionary *param = [url queryParameters];
NSString *openIsSafari = [param objectForKey:@"openInSafari"];
if ( openIsSafari!= nil && ([openIsSafari isEqualToString:@"true"] || [openIsSafari isEqualToString:@"1"])){
[[UIApplication sharedApplication] openURL:url];
return NO;
}
}
return YES;
}
A nice (bad?) point with this solution is that if the link x levels deeper can still open links into safari browser
<a href="http://www.google.com?openInSafari=true">Google in Safari</a>
Always add the protocol in the URL (http, https...)
Upvotes: 5