Making certain links in WKWebView open in Safari, not the Webview - ios

I have a WebView app which contains external links that I wish to have users open within Safari as opposed to the webview itself, on tap. I believe it has something to do with a Navigation Delegate but I am new to iOS Dev and have no idea where to start! Below is my code as it is today. If you can tell me specifically what changes to make and where to put in any code, that would make my life so much easier. Thanks everyone in advance for any help! I think there's a way along the lines of setting a Navigation delegate such that all URL's that start with https://example-root.com/ open normal, in the webview since they are native nav buttons but all other URL's I want to open in safari on tap.
import UIKit
import WebKit
class ViewController: UIViewController {
let webView: WKWebView = {
let prefs = WKWebpagePreferences()
prefs.allowsContentJavaScript = true
let configuration = WKWebViewConfiguration()
configuration.defaultWebpagePreferences = prefs
let webView = WKWebView(frame: .zero, configuration: configuration)
return webView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(webView)
// Do any additional setup after loading the view.
guard let url = URL(string: "https://example-root.com/") else {
return
}
webView.load(URLRequest(url: url))
DispatchQueue.main.asyncAfter(deadline: .now()+5) {
self.webView.evaluateJavaScript("document.body.innerHTML") { result, error in guard lethtml = result as? String, error == nil else {
return
}
}
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
webView.frame = view.bounds
}
}

You're right that you'll need to use the NavigationDelegate to intercept the navigation action. Make your ViewController conform to WKNavigationDelegate and implement the webView(_:decidePolicyFor:decisionHandler:) method:
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url, !url.host.contains("example-root.com") {
UIApplication.shared.open(url)
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
}
Don't forget to set the navigationDelegate property of your WKWebView, you can do this in viewDidLoad with the following:
webView.navigationDelegate = self

Related

Xcode 12.5 and swift 5+ how to open external Urls links from a WKwebView?

Im trying to open external links from a IOS App with local html files. Im using Xcode 12.5 and swift 5+.
The code in my ViewController is simple, just opening the index.html in a folder (www).
The problem now is that i need to open external links http or https in Safari of the iPhone or the iPad and not in the local app. How can i filter that? Thanks for ideas.
Here is my code so far - updated:
import UIKit
import WebKit
import PDFKit
class ViewController: UIViewController, WKUIDelegate {
var webView: WKWebView!
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
// let myURL = URL(string:"www/index.html")
// let myRequest = URLRequest(url: myURL!)
// webView.load(myRequest)
let url = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "www")!
webView.loadFileURL(url, allowingReadAccessTo: url)
let request = URLRequest(url: url)
webView.load(request)
}
// WKWebViewNavigationDelegate
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
// Check for links.
if navigationAction.navigationType == .linkActivated {
// Make sure the URL is set.
guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}
// Check for the scheme component.
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
if components?.scheme == "http" || components?.scheme == "https" {
if navigationAction.targetFrame == nil {
UIApplication.shared.open(url)
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
// Open the link in the external browser.
UIApplication.shared.open(url)
// Cancel the decisionHandler because we managed the navigationAction.
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
}
}
Apparently your webView(_:decidePolicyFor:decisionHandler:) delegate function never gets called. Check it by adding a breakpoint or a print.
To fix this, just set the navigationDelegate of your WKWebView and conform to WKNavigationDelegate:
class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate { // <- conform to WKNavigationDelegate
var webView: WKWebView!
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.navigationDelegate = self // <- set the navigationDelegate property
webView.uiDelegate = self
view = webView
}
}

How to detect AVplayer and get url of current video from WKWebView?

I'm using below code to extract url from UIWebView: it is working fine but, this same code using for WKWebView it's not working anymore. Can anyone help me? The video playing in WKWebView is Inlineplacyback not in fullscreen.
My code is :
NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemBecameCurrent(_:)), name: NSNotification.Name("AVPlayerItemBecameCurrentNotification"), object: nil)
#objc func playerItemBecameCurrent(_ sender : NSNotification){
let playerItem: AVPlayerItem? = sender.object as? AVPlayerItem
if playerItem == nil {
print("player item nil")
return
}
// Break down the AVPlayerItem to get to the path
let asset = playerItem?.asset as? AVURLAsset
let url: URL? = asset?.url
let path = url?.absoluteString
print(path!,"video url")
}
Response URL :
https://r2---sn-po4g5uxa-5hql.googlevideo.com/videoplayback?txp=5531432&sparams=clen%2Cdur%2Cei%2Cgir%2Cid%2Cinitcwndbps%2Cip%2Cipbits%2Citag%2Clmt%2Cmime%2Cmm%2Cmn%2Cms%2Cmv%2Cpcm2%2Cpl%2Cratebypass%2Crequiressl%2Csource%2Cexpire&ip=103.37.181.55&ratebypass=yes&id=o-AM9UWIaxopyYZX4gikGuswG8EMi3dhH_PPBMIqY5cbXj&expire=1554400796&c=MWEB&fvip=4&initcwndbps=481250&ipbits=0&mime=video%2Fmp4&dur=60.093&lmt=1554142002789460&key=yt6&mt=1554379078&itag=18&source=youtube&gir=yes&requiressl=yes&signature=6C68366FC249958BB8E95A5D88074FF8BCB99745.DA113E66DD0B46863BAE52DAA3CAB31FD141F0E5&clen=2708520&mm=31%2C29&mn=sn-po4g5uxa-5hql%2Csn-cvh7knek&ei=vPGlXPOWHIWD8QOO1KBo&ms=au%2Crdu&pcm2=no&pl=24&mv=m&cpn=I9d32bNmeq3kf0jn&cver=2.20190403&ptk=youtube_none&pltype=contentugc
It's video URL not Webpage URL so, please help me how to get this.
Thanks.
This is kind of a hack, but the only way I found to accomplish this.
First set yourself as WKWebView navigation delegate:
self.webView?.navigationDelegate = self
Now listen to all navigation changes, and save the requested url:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let urlStr = navigationAction.request.url?.absoluteString {
//Save presented URL
//Full path can be accessed via self.webview.url
}
decisionHandler(.allow)
}
Now you only need to know when does the new screen become visible, and use the URL you saved (To know the video URL of the new visible screen).
You can do this via listening to UIWindowDidBecomeVisibleNotification notification:
NotificationCenter.default.addObserver(self, selector: #selector(windowDidBecomeVisibleNotification(notif:)), name: NSNotification.Name("UIWindowDidBecomeVisibleNotification"), object: nil)
Then check if the navigation window is not your window, and that means a new screen did open:
#objc func windowDidBecomeVisibleNotification(notif: Notification) {
if let isWindow = notif.object as? UIWindow {
if (isWindow !== self.view.window) {
print("New window did open, check what is the currect URL")
}
}
}
You can try to inject JS in your WKWebView like shown here: https://paulofierro.com/blog/2015/10/12/listening-for-video-playback-within-a-wkwebview
retrieves the full URL from the request property of the navigation action in the webView(_:decidePolicyFor:decisionHandler:) method of WKNavigationDelegate.
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let urlStr = navigationAction.request.url?.absoluteString {
//urlStr is your URL
}
decisionHandler(.allow)
}
also don't forgot to conform protocol
webView.navigationDelegate = self
Using Swift
You can get the html content from url of webview using code below
let docString = webView.stringByEvaluatingJavaScriptFromString("document.documentElement.outerHTML")
This case you'll get the whole html content,
Then look for href links inside the html string
let regex = try! NSRegularExpression(pattern: "<a[^>]+href=\"(.*?)\"[^>]*>")
let range = NSMakeRange(0, docString.characters.count)
let matches = regex.matches(in: docString, range: range)
for match in matches {
let htmlLessString = (docString as NSString).substring(with: match.rangeAt(1))
print(htmlLessString)
}
Check if it is youtube url using
Regular expression: "#https?://(www.)?youtube.com/.[^\s.,"\']+#i"
Another way to achive this
Think outside the box!
You can make an api call to get the url. That seems pretty easy using the web languages like php, .net etc.
The code for getting all urls inside a webpage in PHP (Use whatever language that is okey for you)
$url="http://wwww.somewhere.com";
$data=file_get_contents($url);
$data = strip_tags($data,"<a>");
$d = preg_split("/<\/a>/",$data);
foreach ( $d as $k=>$u ){
if( strpos($u, "<a href=") !== FALSE ){
$u = preg_replace("/.*<a\s+href=\"/sm","",$u);
$u = preg_replace("/\".*/","",$u);
print $u."\n";
}
}
To check one by one if it is youtube url.
$sText = "Check out my latest video here http://www.youtube.com/?123";
preg_match_all('#https?://(www\.)?youtube.com/.[^\s.,"\']+#i', $sText, $aMatches);
var_dump($aMatches);
If you wanted to check if the sample apps are using the same method, get a web debugging proxy and dig on it
Many of the above explanations are taken from other sites.
I Hope it sum up to your need!
Happy coding!
Try this in your ViewController, to add an URL Observer on WKWebView:
override func loadView() {
let webConfig = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfig)
webView.addObserver(self, forKeyPath: "URL", options: .new, context: nil)
view = webView
}
Overriding the observeValue to get url request:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == #keyPath(WKWebView.url) {
let urlRequest:String = webView.url?.absoluteString ?? ""
print(urlRequest)
}
}
Finally... deinit the Observer:
deinit { webView.removeObserver(self, forKeyPath: "URL") }
In WKWebView we need add the configuration for Inlineplacyback is true in WKWebViewConfiguration. If the configuration is set in WKWebView its automatically takes to full screen view.
Below code for reference:
class WebViewController: UIViewController {
lazy var webView: WKWebView! = {
let webView = WKWebView(frame: .zero, configuration: configuration)
webView.translatesAutoresizingMaskIntoConstraints = false
webView.uiDelegate = self
webView.navigationDelegate = self
let request = URLRequest(url: .init(string: "https://developer.apple.com/videos/play/wwdc2020/10188/")!)
webView.load(request)
return webView
}()
lazy var configuration: WKWebViewConfiguration! = {
let configuration = WKWebViewConfiguration()
configuration.allowsInlineMediaPlayback = true
configuration.mediaTypesRequiringUserActionForPlayback = .audio
configuration.allowsPictureInPictureMediaPlayback = true
return configuration
}()
override func loadView() {
super.loadView()
self.view.backgroundColor = .white
self.view.addSubview(self.webView)
// Constraint
NSLayoutConstraint.activate([
self.webView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
self.webView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
self.webView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor),
self.webView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor),
])
}
}
extension WebViewController: WKUIDelegate{
}
extension WebViewController: WKNavigationDelegate{
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse) async -> WKNavigationResponsePolicy {
debugPrint("---------------------------- decidePolicyFor navigationResponse")
return .allow
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: #escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) {
debugPrint("---------------------------- decidePolicyFor navigationAction")
decisionHandler(.allow, .init())
}
}
Click here to see sample output video

handling external links in swift UIWebKit?

I'm working on a webview using swift 4.
I'm loading local html files, in these pages are some links to other websites, but once I click on any of them I want to load them in safari or default browser instead of the WebView (browser).
my webView is called "browser"
Here are my ViewController.swift codes:
import UIKit
import WebKit
class ViewController: UIViewController {
#IBOutlet weak var browser: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let htmlpath = Bundle.main.path(forResource: "kusrc/index", ofType: "html")
let url = URL(fileURLWithPath: htmlpath!)
let request = URLRequest(url: url)
browser.load(request)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Could somebody help me out? most of the results in google were not what I needed...
Is there the possibility to make an if sentence, where it gets asked if the stringprefix ="http:// or https://" then open safari, otherwise "browser"
I think you are looking for this delegate method:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.navigationType == .linkActivated {
guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}
UIApplication.shared.open(url)
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
}
If you want to differentiate according the URL scheme, you can use URLComponents to split the URL in its parts.
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
if components?.scheme == "http" || components?.scheme == "https" {
// insert your code here
}
// edit (a bit more detailed):
import WebKit on the very top of your class: import WebKit
Make your class compliant to the WKNavigationDelegate, by adding it behind the parent class: class ViewController: UIViewController, WKNavigationDelegate
Assign your class to the navigation delegate of the WKWebView: browser.navigationDelegate = self
Add the above code to your class
A gist of how your class should look like in the end: WKWebView open links in Safari
And here is a very nice tutorial about the topic: https://www.hackingwithswift.com/example-code/wkwebview/how-to-control-the-sites-a-wkwebview-can-visit-using-wknavigationdelegate

WebView does not load

I just started developing with swift, so I am sorry if the question is basic/stupid.
I have the following setup, just a test
import WebKit
import UIKit
class ViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://hackingswift.com")!
webView.load(URLRequest(url:url))
webView.allowsBackForwardNavigationGestures = true
}
}
Unfortunately the browser doesn't load. The simulator only shows an empty navigation bar.
Suggestions? I am following a tutorial on hackingswift, so it's supposed to work.
You have to add webView as a subview or make an IBOutlet using Interface builder.
Try this:
class ViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView?
func loadView() {
webView = WKWebView()
webView?.navigationDelegate = self
self.view.addSubview(webView!)
}
override func viewDidLoad() {
super.viewDidLoad()
self.loadView()
let url = URL(string: "https://hackingswift.com")!
webView?.load(URLRequest(url:url))
webView?.allowsBackForwardNavigationGestures = true
}
}
If you want it a bit more simple (without nullable variable), for example:
class ViewController: UIViewController, WKNavigationDelegate {
var webView = WKWebView()
override func viewDidLoad() {
super.viewDidLoad()
webView.navigationDelegate = self
self.view.addSubview(webView)
webView?.allowsBackForwardNavigationGestures = true
self.loadUrl("https://hackingswift.com")
}
func loadUrl(_ url: String) {
if let url = URL(string: url) {
webView.load(URLRequest(url:url))
}
}
}
EDIT: it looks like some websites to load, while others do not, even if they are secure. If I put apple.com in the example, it loads, but a few others do not
Your url should be started with http or https for the webView to load.
Another possible reason is that your url containing an invalid certificate. Add the delegate function below into your code. You have to let WKWebView to bypass the certificate checking. However, this code is never recommended to go into production. You should be careful about what website your webView should and will load.
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let cred = URLCredential(trust: challenge.protectionSpace.serverTrust!)
completionHandler(.useCredential, cred)
}
The problem is this line:
let url = URL(string: "https://hackingswift.com")!
There is no such URL on the Internet, so you're not actually going to see anything. (You won't see anything if you paste that URL into any browser.)
So change that line to this:
let url = URL(string: "https://www.hackingwithswift.com")!
Now run the app, and presto, you'll see the web site:

Loading webpage inside iOS application

If we load a webpage, we can forward it to safari, but this causes users to leave our app. Is there any way so that a user visits any webpage and then come back to our application.
If you want some browser type functionality for devices earlier then iOS7, you can use this inline browser
iOS 9 Update:
Apple has introduced a visible standard interface for browsing the web i.e. SFSafariViewController for iOS 9+ devices.
Example:
func showTutorial() {
if let url = URL(string: "https://www.example.com") {
let config = SFSafariViewController.Configuration()
config.entersReaderIfAvailable = true
let vc = SFSafariViewController(url: url, configuration: config)
present(vc, animated: true)
}
}
You can use a UIWebView to display the page in your application.
I wanted to add a more detailed answer for others in the future (key coding!) :
In the object library, search for WebKit View and add it to your ViewController
Place "import WebKit" under "import UIKit"
Create an outlet from the WebKit View you placed in your ViewController in your code directly under the opening "{"
Under "superViewDidLoad()", all you need is this:
let url = URL(string: "your_url_here"")
and
webview.load(URLRequest(url:url!))
and dassit!
I'll attach a copy of my code just in case I didn't explain it very well:
import UIKit
import WebKit
class WebsiteViewController: UIViewController {
#IBOutlet weak var webview: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://www.eventbrite.com/e/pretty-in-petals-tea-party-tickets-43361370025")
webview.load(URLRequest(url: url!))
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Use a UIWebView Here's how to program one. Let me know if you need more help or examples
#user1706456 UIWebView is deprecated by Apple, You can use WKWebView for it.
Here's code to do that :
step1: Import WebKit
Step2:
//ViewController's LifeCycle.
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
webView.navigationDelegate = self
view = webView
}
Step3 :
In ViewDidLoad:
let myURL = URL(string: "www.google.com")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
Step 4: WKWebView Delegate Methods to handle navigation and loading etc.
//MARK: - WKNavigationDelegate
extension GPWebViewController : WKNavigationDelegate {
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// Refreshing the content in case of editing...
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
}
}

Resources