Skip to content

vuukle/ios_webview_sdk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WebView guide ios

Getting Started

WKWebView is still not available on Interface Builder. However, it is very easy to add them via code.

Example:

import WebKit

override func viewDidLoad() {
    super.viewDidLoad()
    
    self.title = "VUUKLE"
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardShow), name: .UIKeyboardWillShow, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardHide), name: .UIKeyboardWillHide, object: nil)
    setWKWebViewConfigurations()
    addNewButtonsOnNavigationBar()
    configureWebView()
    askCameraAccess()
}

Example for clear cookie:

private func clearAllCookies() {
    let cookieJar = HTTPCookieStorage.shared

    for cookie in cookieJar.cookies! {
        cookieJar.deleteCookie(cookie)
    }
}

private func clearCookiesFromSpecificUrl(yourUrl: String) {
    let cookieStorage: HTTPCookieStorage = HTTPCookieStorage.shared
    let cookies = cookieStorage.cookies(for: URL(string: yourUrl)!)
    for cookie in cookies! {
        cookieStorage.deleteCookie(cookie as HTTPCookie)
    }
}

Use SSO authentication:

// Article to get your PUBLIC_KEY and PRIVATE_KEY
// https://docs.vuukle.com/how-can-i-fetch-my-public-and-private-api-key/

// Config your private and public keys from GlobalConfig.swift
// Keys
//let PUBLISHER_PRIVATE_KEY = "bd3a64e4-7e19-46b2-..........."
//let PUBLISHER_PUBLIC_KEY = "664e0b85-5b2c-4881-............"

// Login user by SSO using email and username

private func loginBySSO(email: String, username: String) {
    let authModel = AuthenticationModel(email: email, username: username)
    
    do {
        let authModelData = try JSONEncoder().encode(authModel).base64EncodedString()
        UserDefaults.standard.setValue(authModelData, forKey: "loginToken")
        let urlString = VUUKLE_IFRAME + "&sso=true&loginToken=" + (UserDefaults.standard.string(forKey: "loginToken") ?? "")
        if let url = URL(string: urlString) {
            wkWebViewWithScript.load(URLRequest(url: url))
        }
    } catch {
        
    }
}

// Logout user SSO

@objc func logoutTapped() {
    UserDefaults.standard.removeObject(forKey: "loginToken")
    removeCookies()
    let urlString = VUUKLE_IFRAME
    if let url = URL(string: urlString) {
        wkWebViewWithScript.load(URLRequest(url: url))
    }
}

Full example:

import UIKit
import WebKit
import AVFoundation
import MessageUI

final class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate {
    
    @IBOutlet weak var containerwkWebViewWithScript: UIView!
    @IBOutlet weak var containerForTopPowerBar: UIView!
    
    @IBOutlet weak var containerTopPowerBarTopConstraint: NSLayoutConstraint!
    @IBOutlet weak var heightWKWebViewConstraint: NSLayoutConstraint!
    @IBOutlet weak var heightScrollView: NSLayoutConstraint!
    @IBOutlet weak var scrollView: UIScrollView!
    
    private var wkWebViewWithScript: WKWebView!
    private var wkWebViewWithEmoji: WKWebView!
    private var wkWebViewForTopPowerBar: WKWebView!
    private var wkWebViewForBottonPowerBar: WKWebView!
    
    private let configuration = WKWebViewConfiguration()
    private var scriptWebViewHeight: CGFloat = 0
    var newWebviewPopupWindow: WKWebView?
    var isKeyboardOpened = false
    let name = "Ross"
    let email = "email@sda"
    
    private var isWkWebViewWithScriptCreated = false
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.title = "VUUKLE"
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardShow), name: .UIKeyboardWillShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardHide), name: .UIKeyboardWillHide, object: nil)
        setWKWebViewConfigurations()
        addNewButtonsOnNavigationBar()
        configureWebView()
        askCameraAccess()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        if isWkWebViewWithScriptCreated {
            wkWebViewForTopPowerBar.reload()
            configureWKWebViewWithScript()
        }
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        isWkWebViewWithScriptCreated = true
    }
    
    @objc func loginBySSOTapped() {
        loginBySSO(email: "sometempmail@yopmail.com", username: "Sample User Name")
    }
    
    @objc func logoutTapped() {
        UserDefaults.standard.removeObject(forKey: "loginToken")
        removeCookies()
        let urlString = VUUKLE_IFRAME
        if let url = URL(string: urlString) {
            wkWebViewWithScript.load(URLRequest(url: url))
        }
    }
    
    func addNewButtonsOnNavigationBar() {
        
        let login = UIBarButtonItem(title: "LOGIN", style: .plain, target: self, action: #selector(loginBySSOTapped))
        login.setBackgroundImage(UIImage(named: "dark_gray"), for: .normal, barMetrics: .default)
        login.setTitleTextAttributes([
                                        NSAttributedStringKey.font: UIFont.systemFont(ofSize: 12),
                                        NSAttributedStringKey.foregroundColor: UIColor.white],
                                     for: .normal)
        let logout = UIBarButtonItem(title: "LOGOUT", style: .plain, target: self, action: #selector(logoutTapped))
        logout.setBackgroundImage(UIImage(named: "dark_gray"), for: .normal, barMetrics: .default)
        logout.setTitleTextAttributes([
                                        NSAttributedStringKey.font: UIFont.systemFont(ofSize: 12),
                                        NSAttributedStringKey.foregroundColor: UIColor.white],
                                      for: .normal)
        
        logout.tintColor = .white
        
        navigationItem.rightBarButtonItems = [logout, login]
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        
        if scrollView.contentOffset.y > 10 {
            self.containerTopPowerBarTopConstraint.constant = -100
            self.view.setNeedsLayout()
            UIView.animate(withDuration: 0.25) {
                self.view.layoutIfNeeded()
            }
        } else {
            self.containerTopPowerBarTopConstraint.constant = 10
            self.view.setNeedsLayout()
            UIView.animate(withDuration: 0.25) {
                self.view.layoutIfNeeded()
            }
        }
    }
    
    // Set wkwebview configurations
    private func setWKWebViewConfigurations() {
        let thePreferences = WKPreferences()
        thePreferences.javaScriptCanOpenWindowsAutomatically = true
        thePreferences.javaScriptEnabled = true
        configuration.preferences = thePreferences
        let source: String = "var meta = document.createElement('meta');" +
            "meta.name = 'viewport';" +
            "meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';" +
            "var head = document.getElementsByTagName('head')[0];" +
            "head.appendChild(meta);"
        
        let script: WKUserScript = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
        let userContentController: WKUserContentController = WKUserContentController()
        
        userContentController.addUserScript(script)
        configuration.userContentController = userContentController
        configuration.processPool = WKProcessPool()
        let cookies = HTTPCookieStorage.shared.cookies ?? [HTTPCookie]()
        cookies.forEach({ if #available(iOS 11.0, *) {
            configuration.websiteDataStore.httpCookieStore.setCookie($0, completionHandler: nil)
        }
        })
    }
    
    func removeCookies() {
        HTTPCookieStorage.shared.removeCookies(since: Date.distantPast)
        print("All cookies deleted")
        
        WKWebsiteDataStore.default().fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in
            records.forEach { record in
                WKWebsiteDataStore.default().removeData(ofTypes: record.dataTypes, for: [record], completionHandler: {})
                print("Cookie ::: \(record) deleted")
            }
        }
    }
    
    @objc func configureWebView() {
        addWKWebViewForScript()
        addWKWebViewForTopPowerBar()
    }
    
    //Hide keyboard
    @objc func keyboardHide() {
        //Code the lines to hide the keyboard and the extra lines you want to execute before keyboard hides.
        self.perform(#selector(keyboardHided), with: nil, afterDelay: 1)
    }
    
    @objc func keyboardHided() {
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.wkWebViewForTopPowerBar.reload()
        }
        isKeyboardOpened = false
    }
    
    //Show keyboard
    @objc func keyboardShow() {
        //Code the lines you want to execute before keyboard pops up.
        isKeyboardOpened = true
    }
    
    
    // Ask permission to use camera For adding photo in the comment box
    func askCameraAccess() {
        AVCaptureDevice.requestAccess(for: AVMediaType.video) { response in
            if response {
                // access granted
            } else {
                // access not granted
            }
        }
    }
    
    // Create WebView for Comment Box
    private func addWKWebViewForScript() {
        
        let thePreferences = WKPreferences()
        thePreferences.javaScriptCanOpenWindowsAutomatically = true
        thePreferences.javaScriptEnabled = true
        configuration.preferences = thePreferences
        
        wkWebViewWithScript = WKWebView(frame: .zero, configuration: configuration)
        wkWebViewWithScript.navigationDelegate = self
        wkWebViewWithScript.uiDelegate = self
        self.containerwkWebViewWithScript.addSubview(wkWebViewWithScript)
        
        configureWKWebViewWithScript()
        
    }
    
    private func configureWKWebViewWithScript() {
        
        wkWebViewWithScript.translatesAutoresizingMaskIntoConstraints = false
        
        wkWebViewWithScript.scrollView.delegate = self
        
        wkWebViewWithScript.topAnchor.constraint(equalTo: self.containerwkWebViewWithScript.topAnchor).isActive = true
        wkWebViewWithScript.bottomAnchor.constraint(equalTo: self.containerwkWebViewWithScript.bottomAnchor).isActive = true
        wkWebViewWithScript.leftAnchor.constraint(equalTo: self.containerwkWebViewWithScript.leftAnchor).isActive = true
        wkWebViewWithScript.rightAnchor.constraint(equalTo: self.containerwkWebViewWithScript.rightAnchor).isActive = true
        
        wkWebViewWithScript.isMultipleTouchEnabled = false
        wkWebViewWithScript.contentMode = .scaleAspectFit
        wkWebViewWithScript.scrollView.bouncesZoom = false
        
        var urlString = VUUKLE_IFRAME
        if let loginToken = UserDefaults.standard.string(forKey: "loginToken") {
            urlString += "&sso=true&loginToken=\(loginToken)"
        }
        if let url = URL(string: urlString) {
            
            let userAgent = USER_AGENT
            var myURLRequest = URLRequest(url: url)
            myURLRequest.setValue(userAgent, forHTTPHeaderField:"user-agent")
            wkWebViewWithScript.load(myURLRequest)
        }
    }
    
    // Create WebView for Top PowerBar
    private func addWKWebViewForTopPowerBar() {
        
        wkWebViewForTopPowerBar = WKWebView(frame: .zero, configuration: configuration)
        self.containerForTopPowerBar.addSubview(wkWebViewForTopPowerBar)
        
        wkWebViewForTopPowerBar.translatesAutoresizingMaskIntoConstraints = false
        wkWebViewForTopPowerBar.topAnchor.constraint(equalTo: self.containerForTopPowerBar.topAnchor).isActive = true
        wkWebViewForTopPowerBar.bottomAnchor.constraint(equalTo: self.containerForTopPowerBar.bottomAnchor).isActive = true
        wkWebViewForTopPowerBar.leftAnchor.constraint(equalTo: self.containerForTopPowerBar.leftAnchor).isActive = true
        wkWebViewForTopPowerBar.rightAnchor.constraint(equalTo: self.containerForTopPowerBar.rightAnchor).isActive = true
        wkWebViewForTopPowerBar.uiDelegate = self
        wkWebViewForTopPowerBar.navigationDelegate = self
        wkWebViewForTopPowerBar.scrollView.isScrollEnabled = false
        
        if let url = URL(string: VUUKLE_POWERBAR) {
            wkWebViewForTopPowerBar.load(URLRequest(url: url))
        }
    }
    
    // Create WebView for Bottom PowerBar
    private func addWKWebViewForBottomPowerBar() {
        
        wkWebViewForBottonPowerBar = WKWebView(frame: .zero, configuration: configuration)

        wkWebViewForBottonPowerBar.translatesAutoresizingMaskIntoConstraints = false
        wkWebViewForBottonPowerBar.uiDelegate = self
        wkWebViewForBottonPowerBar.navigationDelegate = self
        if let url = URL(string: VUUKLE_POWERBAR) {
            wkWebViewForBottonPowerBar.load(URLRequest(url: url))
        }
    }
    
    // MARK: - Clear cookie
    
    private func clearAllCookies() {
        let cookieJar = HTTPCookieStorage.shared
        for cookie in cookieJar.cookies! {
            cookieJar.deleteCookie(cookie)
        }
    }
    
    private func clearCookiesFromSpecificUrl(yourUrl: String) {
        let cookieStorage: HTTPCookieStorage = HTTPCookieStorage.shared
        let cookies = cookieStorage.cookies(for: URL(string: yourUrl)!)
        for cookie in cookies! {
            cookieStorage.deleteCookie(cookie as HTTPCookie)
        }
    }
    
    private func openNewWindow(newURL: String, openWindow: Bool = false) {
        for url in VUUKLE_URLS {
            if newURL.hasPrefix(VUUKLE_MAIL_SHARE) {
                let mailSubjectBody = parsMailSubjextAndBody(mailto: newURL)
                sendEmail(subject: mailSubjectBody.subject, body: mailSubjectBody.body)
                return
            } else if newURL.hasPrefix(VUUKLE_WHATSAPP_SHARE) {
                let appURL = URL(string: newURL)!
                if UIApplication.shared.canOpenURL(appURL) {
                    if #available(iOS 10.0, *) {
                        UIApplication.shared.open(appURL, options: [:], completionHandler: nil)
                    }
                    else {
                        UIApplication.shared.openURL(appURL)
                    }
                }
                return
            } else if newURL.hasPrefix(VUUKLE_MESSENGER_SHARE) {
                let messengerUrlString = replaceLinkSymboles(text: newURL)
                guard let messengerUrl = URL(string: messengerUrlString) else { return }
                UIApplication.shared.open(messengerUrl)
                return
            } else if newURL.hasPrefix(url) {
                wkWebViewWithScript.load(URLRequest(url: URL(string: "about:blank")!))
                print("current opening url:\(newURL)")
                self.openNewsWindow(withURL: newURL)
                return
            } else {
                if openWindow {
                    self.openNewsWindow(withURL: newURL)
                }
                
                return
            }
        }
    }
    
    // MARK: - WKUIDelegate methods
    func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
        let alertController = UIAlertController(title: prompt, message: defaultText, preferredStyle: .alert)
        present(alertController, animated: true)
        alertController.addTextField(configurationHandler: nil)
        
        alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: {_ in
            completionHandler(nil)
        }))
        alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (okAction) in
            completionHandler(alertController.textFields?.first?.text)
        }))
    }
    
    func webView(_ webView: WKWebView, previewingViewControllerForElement elementInfo: WKPreviewElementInfo, defaultActions previewActions: [WKPreviewActionItem]) -> UIViewController? {
        let vc = UIViewController()
        return vc
    }
    
    @available(iOS 13.0, *)
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) {
        
        print("URL :\(navigationAction.request.url?.absoluteString ?? "")")
        if navigationAction.request.url?.absoluteString == VUUKLE_SETTINGS {
            self.openNewWindow(newURL: VUUKLE_SETTINGS, openWindow: true)
        } else if (navigationAction.request.url?.absoluteString ?? "").hasPrefix(VUUKLE_MAIL_TO_SHARE) {
            let mailSubjectBody = parsMailSubjextAndBody(mailto: navigationAction.request.url?.absoluteString ?? "")
            sendEmail(subject: mailSubjectBody.subject, body: mailSubjectBody.body)
        } else if (navigationAction.request.url?.absoluteString ?? "").hasPrefix(VUUKLE_MESSENGER_SHARE) {
            openNewWindow(newURL: navigationAction.request.url?.absoluteString ?? "", openWindow: true)
        }
        decisionHandler(WKNavigationActionPolicy.allow, preferences)
    }
    
    func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
        decisionHandler(.allow)
    }
    
    func webView(_ webView: WKWebView, shouldPreviewElement elementInfo: WKPreviewElementInfo) -> Bool {
        return true
    }
    
    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
        completionHandler()
    }
    
    func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
        completionHandler(true)
    }
    
    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        
        webView.evaluateJavaScript("window.open = function(open) { return function (url, name, features) { window.location.href = url; return window; }; } (window.open);", completionHandler: nil)
        
        
        webView.evaluateJavaScript("window.close = function() { window.location.href = 'myapp://closewebview'; }", completionHandler: nil)
        print( navigationAction.request.url?.absoluteString )
        openNewWindow(newURL: navigationAction.request.url?.absoluteString ?? "", openWindow: true)
        
        return nil
    }
    
    public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void) {
        
        //        self.heightWKWebViewWithScript.constant = scriptWebViewHeight
        if navigationAction.navigationType == .linkActivated {
            openNewWindow(newURL: navigationAction.request.url?.absoluteString ?? "", openWindow: true)
            decisionHandler(.allow)
            return
        } else if navigationAction.navigationType == .other {
            openNewWindow(newURL: navigationAction.request.url?.absoluteString ?? "") //, openWindow: true)
        }
        decisionHandler(.allow)
        return
    }
    
    func openNewsWindow(withURL: String) {
        let newsWindow = VuukleNewViewController()
        newsWindow.wkWebView = self.wkWebViewWithScript
        newsWindow.configuration = self.configuration
        newsWindow.urlString = withURL
        
        self.navigationController?.pushViewController(newsWindow, animated: true)
    }
    
    private func loginBySSO(email: String, username: String) {
        let authModel = AuthenticationModel(email: email, username: username)
        
        do {
            let authModelData = try JSONEncoder().encode(authModel).base64EncodedString()
            UserDefaults.standard.setValue(authModelData, forKey: "loginToken")
            let urlString = VUUKLE_IFRAME + "&sso=true&loginToken=" + (UserDefaults.standard.string(forKey: "loginToken") ?? "")
            if let url = URL(string: urlString) {
                wkWebViewWithScript.load(URLRequest(url: url))
            }
        } catch {
            
        }
    }
}

// MARK: - SEND EMAIL Metods
extension ViewController: MFMailComposeViewControllerDelegate {
    
    func sendEmail(subject: String, body: String) {
        let recipientEmail = ""
        // Show default mail composer
        if MFMailComposeViewController.canSendMail() {
            let mail = MFMailComposeViewController()
            mail.mailComposeDelegate = self
            mail.setToRecipients([recipientEmail])
            mail.setSubject(subject)
            mail.setMessageBody(body, isHTML: false)
            present(mail, animated: true)
            
        } else if let emailUrl = createEmailUrl(to: recipientEmail, subject: subject, body: body) {
            let newMailto = (emailUrl.absoluteString).replacingOccurrences(of: "%20", with: "")
            UIApplication.shared.open(URL(string: newMailto)!)
        }
    }
    
    private func createEmailUrl(to: String, subject: String, body: String) -> URL? {
        let subjectEncoded = subject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        
        let gmailUrl = URL(string: "googlegmail://co?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
        let outlookUrl = URL(string: "ms-outlook://compose?to=\(to)&subject=\(subjectEncoded)")
        let yahooMail = URL(string: "ymail://mail/compose?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
        let sparkUrl = URL(string: "readdle-spark://compose?recipient=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
        let defaultUrl = URL(string: "mailto:\(to)?subject=\(subjectEncoded)&body=\(bodyEncoded)")
        
        if let gmailUrl = gmailUrl, UIApplication.shared.canOpenURL(gmailUrl) {
            return gmailUrl
        } else if let outlookUrl = outlookUrl, UIApplication.shared.canOpenURL(outlookUrl) {
            return outlookUrl
        } else if let yahooMail = yahooMail, UIApplication.shared.canOpenURL(yahooMail) {
            return yahooMail
        } else if let sparkUrl = sparkUrl, UIApplication.shared.canOpenURL(sparkUrl) {
            return sparkUrl
        }
        return defaultUrl
    }
    
    // Get Subject and Body from mailto url.
    func parsMailSubjextAndBody(mailto: String) -> (subject: String, body: String) {
        
        let newMailto = replaceLinkSymboles(text: mailto)
        
        let subjectStartIndex = newMailto.firstIndex(of: "=")!
        let subjectEndIndex = newMailto.firstIndex(of: "&")!
        var subject = String(newMailto[subjectStartIndex..<subjectEndIndex])
        let bodyStartIndex = newMailto.lastIndex(of: "=")!
        var body = String(newMailto[bodyStartIndex...])
        subject.removeFirst()
        body.removeFirst()
        
        return (subject: subject, body: body)
    }
    
    func replaceLinkSymboles(text: String) -> String {
        var newText = text.replacingOccurrences(of: "%20", with: " ")
        newText = newText.replacingOccurrences(of: "%3A", with: ":")
        newText = newText.replacingOccurrences(of: "%2F", with: "/")
        return newText
    }
    
    // MARK: - MFMailComposeViewControllerDelegate
    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
        controller.dismiss(animated: true)
    }
}

About

Vuukle iOS webview

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages