Xcode WKWebView code to allow WebView to process popups - ios

I am totally new to Xcode and the syntax is totally escaping me.
I have one thing left to do and then I've achieved what I need to within Xcode.
I need to insert a function that allows the WebView to process popups, but I have no idea where to begin inserting the code.
This is the current code:
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let webView = WKWebView()
let htmlPath = Bundle.main.path(forResource: "index", ofType: "html")
let folderPath = Bundle.main.bundlePath
let baseUrl = URL(fileURLWithPath: folderPath, isDirectory: true)
do {
let htmlString = try NSString(contentsOfFile: htmlPath!, encoding: String.Encoding.utf8.rawValue)
webView.loadHTMLString(htmlString as String, baseURL: baseUrl)
} catch {
// catch error
}
webView.navigationDelegate = self
view = webView
}
}
How do I edit this in order to allow popups?
Please remember that I don't know how to add the solutions that I'm finding, so please show me where to insert the snippet as well.

Try this code, hope it helps!
class ViewController: UIViewController {
var webView: WKWebView!
var popupWebView: WKWebView?
var urlPath: String = "WEBSITE_URL"
override func viewDidLoad() {
super.viewDidLoad()
setupWebView()
loadWebView()
}
//MARK: Setting up webView
func setupWebView() {
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
preferences.javaScriptCanOpenWindowsAutomatically = true
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
webView = WKWebView(frame: view.bounds, configuration: configuration)
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webView.uiDelegate = self
webView.navigationDelegate = self
view.addSubview(webView)
}
func loadWebView() {
if let url = URL(string: urlPath) {
let urlRequest = URLRequest(url: url)
webView.load(urlRequest)
}
}
}
extension ViewController: WKUIDelegate {
//MARK: Creating new webView for popup
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
popupWebView = WKWebView(frame: view.bounds, configuration: configuration)
popupWebView!.autoresizingMask = [.flexibleWidth, .flexibleHeight]
popupWebView!.navigationDelegate = self
popupWebView!.uiDelegate = self
view.addSubview(popupWebView!)
return popupWebView!
}
//MARK: To close popup
func webViewDidClose(_ webView: WKWebView) {
if webView == popupWebView {
popupWebView?.removeFromSuperview()
popupWebView = nil
}
}
}
}

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

Getting an ios storyboard webView app to render in safe area

Probably a simple fix but I'm trying to get my simple ios storyboard app to render in the safe area on my iPhone 11.
It's currently taking up the entire view space and not using the safe area. I'm not confident whether I need to make any changes to the code or is it simply a setting in the storyboard view which is not on.
It's my first app. Thanks for your help.
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate {
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let myURL = URL(string:"https://google.com")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
if #available(iOS 11.0, *) {
webView.scrollView.contentInsetAdjustmentBehavior = .never;
}
webView.navigationDelegate = self
webView.allowsBackForwardNavigationGestures = true
}
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webConfiguration.dataDetectorTypes = [.all]
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
view = webView
}
}
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
guard
let url = navigationAction.request.url else {
decisionHandler(.cancel)
return
}
let string = url.absoluteString
if (string.contains("mailto:")) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
decisionHandler(.cancel)
return
}
if (string.contains("sms:")) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
}
class FullScreenWKWebView: WKWebView {
override var safeAreaInsets: UIEdgeInsets {
return UIEdgeInsets(top: 100, left: 0, bottom: 0, right: 0)
}
}
That's happening because you're setting the webView as the controller's view. I think the easiest approach is by setting the webView as a subView of the viewController main view. Start by moving the initialisation code from the loadView method to the viewDidLoad, add the webView as a subView and finally constrainer the webView anchors to the safeArea anchors.
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate {
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let webConfiguration = WKWebViewConfiguration()
webConfiguration.dataDetectorTypes = [.all]
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
webView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(webView)
NSLayoutConstraint.activate([
webView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
webView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
webView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
let myURL = URL(string:"https://google.com")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
if #available(iOS 11.0, *) {
webView.scrollView.contentInsetAdjustmentBehavior = .never;
}
webView.navigationDelegate = self
webView.allowsBackForwardNavigationGestures = true
}
}
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
guard
let url = navigationAction.request.url else {
decisionHandler(.cancel)
return
}
let string = url.absoluteString
if (string.contains("mailto:")) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
decisionHandler(.cancel)
return
}
if (string.contains("sms:")) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
}
If the previous option is not valid, you can use your code and change the webView bounds using the viewSafeAreaInsetsDidChange method, making it aware of the safe area insets. Just add the following code:
override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()
view.bounds = view.safeAreaLayoutGuide.layoutFrame
}

IOS WKWebview Cache Clearing

I have made a very simple WKWebview IOS app that embeds my Wordpress website ...
import UIKit
import WebKit
class ViewController: UIViewController {
private lazy var url = URL(string: "http://www.myblog.com")!
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)
}
}
There is a caching plugin on the actual website and I would like for whenever the app loads on a users device - for the app to pull in a fresh copy of the url. Is this possible?
Can anyone advise where to start or how i can adjust my current code to get started?
Thank you!
Push notifications has nothing to do with the WKWebview.
First you need to handle events in the backend/wordpress. From the mobile side, you need the following:
1- Generating a P12 or P8 certificate and put it on the server to trust APN
2- Register for push notifications in AppDelegate class.
3- Handle receiving a push notification in AppDelegate class too.

WKWebView: mailto links in html content not opening mail app

I created a very simple iOS app (Swift 5). It's just a WKWebView that loads my PWA url.
Everything works fine except all Mail me links. When I click them, nothing happens, my mail app doesn't open.
This is the code of my ViewController.swift:
//
// ViewController.swift
// panel
//
// Created by kevin on 25/07/2019.
// Copyright © 2019 umono. All rights reserved.
//
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate {
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let myURL = URL(string:"https://someUrlToMyApp.appspot.com")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
if #available(iOS 11.0, *) {
webView.scrollView.contentInsetAdjustmentBehavior = .never;
}
}
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webConfiguration.dataDetectorTypes = [.all]
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
view = webView
}
}
EDIT:
Thx guy's, here is my working code:
//
// ViewController.swift
// panel
//
// Created by kevin on 25/07/2019.
// Copyright © 2019 umono. All rights reserved.
//
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate {
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let myURL = URL(string:"https://someUrlToMyApp.appspot.com")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
if #available(iOS 11.0, *) {
webView.scrollView.contentInsetAdjustmentBehavior = .never;
}
webView.navigationDelegate = self
}
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webConfiguration.dataDetectorTypes = [.all]
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
view = webView
}
}
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
guard
let url = navigationAction.request.url else {
decisionHandler(.cancel)
return
}
let string = url.absoluteString
if (string.contains("mailto:")) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
}
One way to do what you want would be to implement WKNavigationDelegate:
import UIKit
import WebKit
class ViewController: UIViewController {
#IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
guard
let file = Bundle.main.path(forResource: "test", ofType: "html"),
let html = try? String(contentsOfFile: file) else {
return
}
webView.navigationDelegate = self
webView.loadHTMLString(html, baseURL: nil)
}
#IBAction func didTapButton(_ sender: Any) {
let email = "email#email.com"
guard
let url = URL(string: "mailto:\(email)") else {
return
}
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
guard
let url = navigationAction.request.url,
let scheme = url.scheme else {
decisionHandler(.cancel)
return
}
if (scheme.lowercased() == "mailto") {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
// here I decide to .cancel, do as you wish
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
}
Here you have a ViewController that has webView as an outlet, this WKWebView would load an html file like this:
Mail me
And I also added in storyboard a button just for reference, which would have the IBAction didTapButton described above.
The key here is:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: #escaping (WKNavigationActionPolicy) -> Void)
Which would give you the URL and let you decide what policy is suitable for it. Here I check if it contains mailto: as I already know this is what you're interested in so if it does, I simply open the URL as I would do if the user presses an UIButton visible on screen.
Hope it helps, cheers!
LE: Make sure you run on a real device (simulators don't have Mail app installed), also make sure you have the Mail app installed, cause I didn't..

WKWebView evaluateJavaScript not returning html

I am trying to parse the html returned from a WKWebView load() with evaluateJavaScript but it never prints anything. Am I doing this right? Any other ways? didFinish does print.
import UIKit
import WebKit
class MyWebViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView(frame: self.view.frame)
webView.navigationDelegate = self
let url = NSURL (string: "https://google.com");
let request = NSURLRequest(url: url! as URL)
webView.load(request as URLRequest)
self.view.addSubview(webView)
self.view.sendSubview(toBack: webView)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.documentElement.outerHTML.toString()", completionHandler: { (html: AnyObject?, error: NSError?) in
print(html!)
} as? (Any?, Error?) -> Void)
print("didFinish")
}
}
Using evaluateJavaScript with a WKWebView is a bit tricky.
Since I think this answer would be useful to many people, rather than address your specific question with a short code snippet and a comment that you need to implement WKScriptMessageHandler, I'm going to post a full, complete example that you can use to see how everything works together.
To use this, create a "Single View Application" iOS project in Xcode and paste this over the default ViewController.swift file.
//
// WebViewController.swift
// WKWebViewExample
//
// Created by par on 4/2/17.
// Copyright © 2017 par. All rights reserved. MIT License.
//
import UIKit
import WebKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let webViewController = WebViewController()
// install the WebViewController as a child view controller
addChildViewController(webViewController)
let webViewControllerView = webViewController.view!
view.addSubview(webViewControllerView)
webViewControllerView.translatesAutoresizingMaskIntoConstraints = false
webViewControllerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
webViewControllerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
webViewControllerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
webViewControllerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
webViewController.didMove(toParentViewController: self)
}
}
class WebViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {
private var webView: WKWebView!
private var webViewContentIsLoaded = false
init() {
super.init(nibName: nil, bundle: nil)
self.webView = {
let contentController = WKUserContentController()
contentController.add(self, name: "WebViewControllerMessageHandler")
let configuration = WKWebViewConfiguration()
configuration.userContentController = contentController
let webView = WKWebView(frame: .zero, configuration: configuration)
webView.scrollView.bounces = false
webView.navigationDelegate = self
return webView
}()
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(webView)
webView.translatesAutoresizingMaskIntoConstraints = false
webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
webView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
webView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if !webViewContentIsLoaded {
let url = URL(string: "https://stackoverflow.com")!
let request = URLRequest(url: url)
webView.load(request)
webViewContentIsLoaded = true
}
}
private func evaluateJavascript(_ javascript: String, sourceURL: String? = nil, completion: ((_ error: String?) -> Void)? = nil) {
var javascript = javascript
// Adding a sourceURL comment makes the javascript source visible when debugging the simulator via Safari in Mac OS
if let sourceURL = sourceURL {
javascript = "//# sourceURL=\(sourceURL).js\n" + javascript
}
webView.evaluateJavaScript(javascript) { _, error in
completion?(error?.localizedDescription)
}
}
// MARK: - WKNavigationDelegate
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// This must be valid javascript! Critically don't forget to terminate statements with either a newline or semicolon!
let javascript =
"var outerHTML = document.documentElement.outerHTML.toString()\n" +
"var message = {\"type\": \"outerHTML\", \"outerHTML\": outerHTML }\n" +
"window.webkit.messageHandlers.WebViewControllerMessageHandler.postMessage(message)\n"
evaluateJavascript(javascript, sourceURL: "getOuterHMTL")
}
// MARK: - WKScriptMessageHandler
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let body = message.body as? [String: Any] else {
print("could not convert message body to dictionary: \(message.body)")
return
}
guard let type = body["type"] as? String else {
print("could not convert body[\"type\"] to string: \(body)")
return
}
switch type {
case "outerHTML":
guard let outerHTML = body["outerHTML"] as? String else {
print("could not convert body[\"outerHTML\"] to string: \(body)")
return
}
print("outerHTML is \(outerHTML)")
default:
print("unknown message type \(type)")
return
}
}
}
I know it has been a while since this was answered and I found that this didn't really work for me. I used the below instead.
I could not get the evaluateJavaScript to work until i added the setTimeout() function. For what it is worth here is my code below.
webView.evaluateJavaScript("setTimeout(function(){
// Do something here
},10);") { (result1, error) in
if error == nil {
print(result1 ?? "")
}
}

Resources