Creating a custom webview from scratch - ios

I am in a situation where I want more flexibility for webview than the offered by UIWebview and not at all by WKWebview. Things like customizing web request before start eg sending headers and redirections. Also as we know UIWebview officially deprecated now in iOS 12.
I'm looking forward to a generic webview now, I know its possible as open source examples say Firefox for iOS.
If you've been using any of your projects or know please tell me. Or if you can give me some information how can this be achieved or any tutorails links would be helpful.

Declare the WebView object
var webView: WKWebView!
//Load the data in webView
func loadUrl() {
var webView : WKWebView!
let script: WKUserScript = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
let userContentController: WKUserContentController = WKUserContentController()
let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
userContentController.addUserScript(script)
webView = WKWebView(frame: CGRect.zero, configuration: configuration)
webView.uiDelegate = self
webView.navigationDelegate = self
webView.allowsLinkPreview = true
//Load the Data from URL
let url = URL(string: "Your URL")
var urlRequest = URLRequest(url: url! as URL)
urlRequest.setValue("application/json", forHTTPHeaderField: "Accept")
urlRequest.setValue("user-agent", forHTTPHeaderField: "User-Agent")
webView.load(urlRequest)
//Load the Data from local HTML
do {
guard let filePath = Bundle.main.path(forResource: "Your-HTML-File", ofType: "html")
else {
print ("FILE READING ERROR")
return
}
//get the content of HTML File
let contents = try String(contentsOfFile: filePath , encoding: .utf8)
webView.loadHTMLString(contents, baseURL:URL(fileURLWithPath: Bundle.main.bundlePath))
}
catch {
print ("FILE HTML ERROR")
}
}
//Delegate Method that called when webView finish Loading.
//You can apply css or style here
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
///Local CSS File
guard let path = Bundle.main.path(forResource: "Local-CSS-File", ofType: "css") else { return }
let script = "var head = document.getElementsByTagName('head')[0];var link = document.createElement('link');link.rel = 'stylesheet';link.type = 'text/css';link.href = '\(path)';link.media = 'all';head.appendChild(link);"
webView.evaluateJavaScript(script) {(result, error) in
if let error = error {
print(error)
}
}
//External CSS File
let externalCSSLink = "your-css-file-url"
let script2 = "var head = document.getElementsByTagName('head')[0];var link = document.createElement('link');link.rel = 'stylesheet';link.type = 'text/css';link.href = '\(externalCSSLink)';link.media = 'all';head.appendChild(link);"
webView.evaluateJavaScript(jsString1) {(result, error) in
if let error = error {
print(error)
}
}
}

Related

How to hide header and footer in WKWebView using Js and ios-14

How to hide header and footer in WKWebView in swift in IOS 14 I am using below code for loadiing thr website inside webview.
webview.load(URLRequest.init(url: URL.init(string: "myURLHere)!))
Try this code using CSS
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let cssString = "header {display: none;}"
let script = "var style = document.createElement('style'); style.innerHTML = '\(cssString)'; document.head.appendChild(style);"
webView.evaluateJavaScript(script,
completionHandler: { (response, error) -> Void in
print(error)
webView.isHidden = false
})
let cssString1 = "footer {display: none;}"
let script1 = "var style = document.createElement('style'); style.innerHTML = '\(cssString1)'; document.head.appendChild(style);"
webView.evaluateJavaScript(script1,
completionHandler: { (response, error) -> Void in
webView.isHidden = false
})
}
You need to use delegate function while loading the website like
self.webview.uiDelegate = self
webview.load(URLRequest.init(url: URL.init(string: 'url here')!))
and then inside the delegate method use following code.
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webview.evaluateJavaScript("document.getElementById(\"header_id\").style.display='none';document.getElementById(\"footer_id\").style.display='none';", completionHandler: { (res, error) -> Void in
//Here you can check for results if needed (res) or whether the execution was successful (error)
})
}
We can use WKUserContentController to inject script to hide header, footer or something which you want
let contentController = WKUserContentController()
let script = "var style = document.createElement('style'); style.innerHTML = 'header {display: none;}'; document.head.appendChild(style);"
let scriptInjection = WKUserScript(source: script, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
contentController.addUserScript(scriptInjection)
let configuration = WKWebViewConfiguration()
configuration.websiteDataStore = .nonPersistent()
configuration.userContentController = contentController
let webview = WKWebView(frame: .zero, configuration: configuration)

Read local storage data using WKWebView

I need to read value stored in the local storage of WKWbview.
I tried using the below code but getting nil.
I am able to write values in local storage but facing difficulty in reading values from it.
let script = "localStorage.getItem('token')"
wkWebView.evaluateJavaScript(script) { (token, error) in
print("token = \(token)")
}
WKWebView init code:
// 1
let accessToken = UserDefaults.standard.value(forKey: "token") as? String
// 2
let refreshToken = UserDefaults.standard.value(forKey: "RefreshToken") as? String
// 3
let configuration = WKWebViewConfiguration()
// 4
let contentController = WKUserContentController()
let accessTokenScript = "javascript: localStorage.setItem('token', '\(accessToken!)')"
// 5
let userAccessTokenScript = WKUserScript(source: accessTokenScript, injectionTime: WKUserScriptInjectionTime.atDocumentStart, forMainFrameOnly: false)
// 6
contentController.addUserScript(userAccessTokenScript)
configuration.userContentController = contentController
self.wkWebView = WKWebView(frame: controller.view.bounds, configuration: configuration)
You need to inject this script when the website has already loaded:
your WKWebView needs to assign the navigationDelegate
webView.navigationDelegate = self
inject the script when the website was loaded completely
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
//you might want to edit the script, with the escape characters
let script = "localStorage.getItem(\"token\")"
wkWebView.evaluateJavaScript(script) { (token, error) in
if let error = error {
print ("localStorage.getitem('token') failed due to \(error)")
assertionFailure()
}
print("token = \(token)")
}
}

Read HTML elements in webView(_:decidePolicyFor:decisionHandler:)

I'm showing html page in a WKWebView. My html contains links with embedded pdf as follow:
<td><div class="truncate"><a id="allegato1" class="link" href="data:octet-stream;base64,JVBERi0xLjIgCiXi48/
.........................................................
Ao3OTA2MiAKJSVFT0YgCg==%0A" download="FATCLI_19244324.PDF">FATCLI_19244324.PDF</a></div></td>
Now, I have to intercept click in the above links, save pdf on disk and then open the file with a reader. I'm able to do this as follow:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Swift.Void) {
if navigationAction.navigationType == .linkActivated, let url = navigationAction.request.url {
if url.scheme == "data" {
let splitted = url.absoluteString.components(separatedBy: "base64,")
if splitted.count == 2 {
let documentName = .... // What should I do?
if let data = Data(base64Encoded: splitted[1]) {
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(documentName)
do {
try data.write(to: fileURL, options: .atomicWrite)
// Show pdf with via UIDocumentInteractionController
decisionHandler(.cancel)
return
} catch {
// Manage the error
decisionHandler(.cancel)
return
}
}
}
}
decisionHandler(.allow)
} else {
decisionHandler(.allow)
}
}
What I am trying to do is find a way to read document name from element download or from tag a value (FATCLI_19244324.PDF in this case). But it seems to be no way to do this in webView(_:decidePolicyFor:decisionHandler:)method.
Can anyone help me?
I solved my problem by injecting some javascript in my WKWebView.
override func viewDidLoad() {
super.viewDidLoad()
let configuration = WKWebViewConfiguration()
let userController:WKUserContentController = WKUserContentController()
userController.add(self, name: "linkClicked")
let js:String = """
var links = document.getElementsByClassName("link");
Array.prototype.forEach.call(links, function(link) {
// Disable long press
link.style.webkitTouchCallout='none';
link.addEventListener("click", function() {
var messageToPost = {'download': link.getAttribute("download"), 'href': link.getAttribute("href")}; window.webkit.messageHandlers.linkClicked.postMessage(messageToPost);
},false);
});
"""
let userScript:WKUserScript = WKUserScript(source: js, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
userController.addUserScript(userScript)
WKUserContentController
configuration.userContentController = userController;
myWebView = WKWebView(frame: webViewContainer.bounds, configuration: configuration)
......
}
In this way I can read download and href elements in userContentController(_:didReceive:) method and save pdf on disk with right name.
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let body = message.body as? [String: Any] else { return }
guard let documentName = body["download"] as? String, let href = body["href"] as? String, let url = URL(string: href) else { return }
if url.scheme == "data" {
let splitted = url.absoluteString.components(separatedBy: "base64,")
if splitted.count == 2 {
if let data = Data(base64Encoded: splitted[1]) {
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(documentName)
do {
try data.write(to: fileURL, options: .atomicWrite)
// Show pdf with via UIDocumentInteractionController
} catch {
// Manage the error
}
}
}
}
}

WKWebView load request in device not working, but simulator is working

I have a question about WKWebview.
First, I have a UITabbarViewController(tab1, tab2), and two ViewControllers.
In ViewController1 have two button (button1, button2) which action is click the tabbar tab2 to present the ViewController2 and bring different url string.
In ViewController2 have a WKWebView, and load the request of url string.
I work fine in simulator, but build in device and work fine at first time and the then I tap the tabbar tab1 and back to ViewController1.
I click the button2 and present the ViewController2.
And I also set breakpoint, the WKWebview have run the load request function.
But the WKWebview also show me the button1 url content, not button2 url content. When I back and click button2 again, it's work. Why the WKWebView don't refresh at first time?
Thanks.
Xcode 9.4.1, device iOS 12.0.1(16A404)
class ViewController1: UIViewController {
#objc func btnTapped(sender: UIButton) {
if let vc = self.navigationController?.parent as? ViewController {
if let number = item?.number {
let domainPath = “\(customURL!.absoluteString)"
let extPath = "detail/\(number)/&topPageBack=list"
let encodestr = extPath.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
let finalPath = domainPath.appending("/").appending(encodestr!)
if let url = URL(string: finalPath) {
vc.loadSpecificURL = url
}
vc.tabBarView.buttonClick(tag: 1)
}
}
}
}
class ViewController2: UIViewController {
lazy var webViewController: CustomWKWebViewController = {
let controller = CustomWKWebViewController()
controller.wKWebViewPage = .searchPage
return controller
}()
var loadURL: URL?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let url = loadURL {
webViewController.reloadWebView(url: url)
loadURL = nil
}
}
}
class CustomWKWebViewController: UIViewController {
private func settingWebView() -> WKWebView {
let userScript = WKUserScript(source: source, injectionTime: .atDocumentStart, forMainFrameOnly: true)
let userContentController = WKUserContentController()
userContentController.add(self, name: "Login")
userContentController.add(self, name: "Other")
userContentController.add(self, name: “LD”)
userContentController.addUserScript(userScript)
let config = WKWebViewConfiguration()
config.userContentController = userContentController
webView = WKWebView(frame: .zero, configuration: config)
webView!.navigationDelegate = self
webView!.uiDelegate = self
webView!.scrollView.bounces = false
webView!.allowsBackForwardNavigationGestures = true
webView!.backgroundColor = .clear
webView!.scrollView.backgroundColor = .clear
if let request = getRequest() {
webView!.load(request)
}
return webView!
}
func reloadWebView(url: URL?) {
if let url = url, let webView = self.webView {
var requestForBlank = URLRequest(url: URL(string: "about:blank")!, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 3)
requestForBlank.httpMethod = "POST"
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 3)
request.httpMethod = "POST"
webView.load(requestForBlank)
webView.load(request)//breakpoint always break here, but the view don't load request.
}
}
}
First you check if web url not nil, then also call the else part of if.
if let url = webview.url {
webview.reload()
} else {
webview.load(URLRequest(url: originalURL))
}

Load UIImage into a WebView Swift 4

Rather than loading a WebView from a URL like this
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: imageUrl.urlString)
let request = URLRequest(url: url!)
webView.load(request)
}
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
view = webView
}
How can I load a UIImage onto the WebView in Swift 4?
Try Something like this.
let html = "<html><img src=\"datadb/DestinationGuide/Images/YourImage.png\"/> </html>"
//Save this html in `DocumentDirectory`
let saveHTML = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("temp.html")
try? html.data(using: .utf8)!.write(to: saveHTML)
//Now load this `temp.html` file in webView
webView.loadRequest(URLRequest(url: saveHTML))

Resources