import UIKit
import WebKit
class WebViewController: UIViewController {
private var webView: WKWebView!
private var progressView: UIProgressView!
private let urlString: String
init(urlString: String) {
self.urlString = urlString
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
let configuration = WKWebViewConfiguration()
configuration.allowsInlineMediaPlayback = true
// JavaScript message handler
let contentController = WKUserContentController()
contentController.add(self, name: "messageHandler")
configuration.userContentController = contentController
webView = WKWebView(frame: .zero, configuration: configuration)
webView.navigationDelegate = self
webView.uiDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
setupProgressView()
setupObservers()
loadURL()
}
private func setupProgressView() {
progressView = UIProgressView(progressViewStyle: .default)
progressView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(progressView)
NSLayoutConstraint.activate([
progressView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
progressView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
progressView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
private func setupObservers() {
webView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)
webView.addObserver(self, forKeyPath: #keyPath(WKWebView.title), options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "estimatedProgress" {
progressView.progress = Float(webView.estimatedProgress)
progressView.isHidden = webView.estimatedProgress == 1.0
} else if keyPath == "title" {
title = webView.title
}
}
private func loadURL() {
guard let url = URL(string: urlString) else { return }
let request = URLRequest(url: url)
webView.load(request)
}
// MARK: - JavaScript Execution
func executeJavaScript(_ script: String, completion: ((Any?, Error?) -> Void)? = nil) {
webView.evaluateJavaScript(script, completionHandler: completion)
}
deinit {
webView.removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress))
webView.removeObserver(self, forKeyPath: #keyPath(WKWebView.title))
}
}
// MARK: - WKNavigationDelegate
extension WebViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url {
// Handle custom URL schemes
if url.scheme == "myapp" {
print("Handle custom URL: \(url)")
decisionHandler(.cancel)
return
}
// Block certain domains
if url.host == "blocked.com" {
decisionHandler(.cancel)
return
}
}
decisionHandler(.allow)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("Finished loading")
// Inject JavaScript
let js = """
window.webkit.messageHandlers.messageHandler.postMessage({
action: 'pageLoaded',
url: window.location.href
});
"""
executeJavaScript(js)
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
print("Failed to load: \(error.localizedDescription)")
}
}
// MARK: - WKUIDelegate
extension WebViewController: WKUIDelegate {
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in
completionHandler()
})
present(alert, animated: true)
}
}
// MARK: - WKScriptMessageHandler
extension WebViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let dict = message.body as? [String: Any],
let action = dict["action"] as? String else {
return
}
switch action {
case "pageLoaded":
if let url = dict["url"] as? String {
print("Page loaded: \(url)")
}
default:
print("Unknown action: \(action)")
}
}
}