ChatGPT解决这个技术问题 Extra ChatGPT

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?

The accepted answer below will not work in all cases.
I've added a better answer. Please mark my answer as the accepted one.

V
Vlad

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

drawnonward, would you mind updating your answer with reference to the answer and comments below.
How to go back to the application once the user closes the browser?
@ Jhonny Everson: You have no control over what happens after any external app (including Safari) is closed. If you want to get back to your app when the user is done browsing, don't open up Safari, just use the UIWwbView and a "Done"-button.
Worked like a charm with local HTML file.
I think, in Swift, a switch is preferable for enum types
D
DiegoFrings

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
}

any idea how to do this ONLY with URLS that have https:// http:// or mailto:? using swift? Question here needs a swift answer! stackoverflow.com/questions/2532453/…
@JedGrant swift version now on stackoverflow.com/questions/2532453/…
make sure to use UIWebViewDelegate
M
MonsieurDart

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.


+1 I had the same issue. Solution was to check for UIWebViewNavigationTypeLinkClicked as the request type, THEN open the URL and return NO, otherwise return YES.
Wade you should post your comment as an answer
K
KPM

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.


Note: the regular expression is derived from stackoverflow.com/questions/106179/…
Yes to the point about filtering incoming requests, no to the hostname parsing. A better approach would be to filter based on the URL scheme. On iOS 8.4 (simulator), I got "applewebdata" used as the scheme for anchor links, but that may vary with target version.
Good idea MandisaW. To allow anchor links I check for the "file" scheme if (request.URL?.scheme != "file")
B
BaseZen

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.


N
Nazmul Hasan

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

d
danfordham

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

I
IanS

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

Can you post a Swift 4 version of this?
M
Michael Platt

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

G
Gennadiy Ryabkin

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