Instantly share code, notes, and snippets.
Created
February 16, 2022 14:06
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save LeeKahSeng/7f01d9ed7db4925debf24ba6bcf17dd9 to your computer and use it in GitHub Desktop.
Full sample code for article "How to Hijack WKWebView Navigation Actions" (https://swiftsenpai.com/development/hijack-wkwebview-navigation/)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import WebKit | |
// MARK: - Code related to "How to Hijack WKWebView Navigation Actions" starts here. | |
// https://swiftsenpai.com/development/hijack-wkwebview-navigation/ | |
class ColorViewController: UIViewController { | |
var screenColor: UIColor? | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
view.backgroundColor = screenColor | |
} | |
} | |
enum TransitionType: String { | |
case present | |
case push | |
} | |
enum ActionType: String { | |
case navigate | |
} | |
extension HijackViewController: WKNavigationDelegate { | |
func webView(_ webView: WKWebView, | |
decidePolicyFor navigationAction: WKNavigationAction, | |
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { | |
if | |
navigationAction.navigationType == WKNavigationType.linkActivated, | |
let url = navigationAction.request.url { | |
// Check is user request to perform custom navigate action | |
if | |
let scheme = url.scheme, | |
let actionType = ActionType(rawValue: scheme), | |
actionType == .navigate { | |
// Extract transition type from URL | |
guard | |
let host = url.host, | |
let transitionType = TransitionType(rawValue: host) else { | |
return | |
} | |
// Extract RGB from URL query items | |
guard | |
let component = URLComponents(string: url.absoluteString), | |
let r = component.queryItems?.first(where: { $0.name == "r" })?.value, | |
let g = component.queryItems?.first(where: { $0.name == "g" })?.value, | |
let b = component.queryItems?.first(where: { $0.name == "b" })?.value else { | |
return | |
} | |
// Convert rgb String to Double (Only available in Swift 5.5) | |
guard | |
let red = Double(r), | |
let green = Double(g), | |
let blue = Double(b) else { | |
return | |
} | |
let viewController = ColorViewController() | |
let screenColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0) | |
viewController.screenColor = screenColor | |
// Perform navigation based on transition type | |
switch transitionType { | |
case .present: | |
present(viewController, animated: true) | |
case .push: | |
navigationController?.pushViewController(viewController, animated: true) | |
} | |
} else { | |
// Load URL as usual | |
let req = URLRequest(url: url) | |
webView.load(req) | |
} | |
decisionHandler(.cancel) | |
return | |
} | |
decisionHandler(.allow) | |
} | |
} | |
// MARK: - Following code showcases JavaScript injection technique discussed in "Injecting JavaScript Into Web View In iOS". | |
// https://swiftsenpai.com/development/web-view-javascript-injection/ | |
class HijackViewController: UIViewController { | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
configureWebView() | |
} | |
private func configureWebView() { | |
let css = """ | |
html, body { | |
overflow-x: hidden; | |
} | |
body { | |
background-color: #333333; | |
line-height: 1.5; | |
color: white; | |
padding: 10; | |
font-weight: 600; | |
font-family: -apple-system; | |
} | |
a { | |
color: white; | |
} | |
""" | |
// Make CSS into single liner | |
let cssString = css.components(separatedBy: .newlines).joined() | |
// Create JavaScript that loads the CSS | |
let javaScript = """ | |
var element = document.createElement('style'); | |
element.innerHTML = '\(cssString)'; | |
document.head.appendChild(element); | |
""" | |
// Create user script that inject the JavaScript after the HTML finishes loading | |
let userScript = WKUserScript(source: javaScript, | |
injectionTime: .atDocumentEnd, | |
forMainFrameOnly: true) | |
// Set user script to a configuration object and load it into the webView | |
let userContentController = WKUserContentController() | |
userContentController.addUserScript(userScript) | |
let configuration = WKWebViewConfiguration() | |
configuration.userContentController = userContentController | |
let webView = WKWebView(frame: CGRect.zero, configuration: configuration) | |
// Show web view on screen | |
view.addSubview(webView) | |
webView.layer.cornerRadius = 20.0 | |
webView.layer.masksToBounds = true | |
webView.translatesAutoresizingMaskIntoConstraints = false | |
NSLayoutConstraint.activate([ | |
webView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 20.0), | |
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0), | |
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0), | |
webView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor, constant: -20.0), | |
]) | |
webView.navigationDelegate = self | |
// Load content to webView | |
let htmlContent = | |
""" | |
<h1>Swift Senpai Demo</h1> | |
<h2>This is a web view</h2> | |
<ul> | |
<li><a href="navigate://present?r=255&g=255&b=0" target="_blank">Present Yellow Screen</a></li> | |
<li><a href="navigate://push?r=255&g=0&b=0" target="_blank">Push Red Screen</a></li> | |
</ul> | |
""" | |
// Prepend `<meta>` viewport element to the HTML so that it will scale correctly in a mobile device | |
let metaTag = "<meta name=\"viewport\" content=\"user-scalable=no, width=device-width\">" | |
let html = "\(metaTag)\(htmlContent)" | |
// Load HTML string to web view in main thread | |
webView.loadHTMLString(html, baseURL: nil) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
nice.
But How Can view handle the webView data (cache) when navigation stack(webView navigation). and how we restore and reload the webPage when we poptoViewcontroller