197 lines
7.3 KiB
Swift
197 lines
7.3 KiB
Swift
import ComposeApp
|
|
import UIKit
|
|
import WebKit
|
|
|
|
/// 应用内网页(对齐 Android [WebViewActivity]),含广告过滤与页内导航。
|
|
final class InAppWebViewController: UIViewController {
|
|
private let urlString: String
|
|
private let pageTitle: String?
|
|
private var webView: WKWebView?
|
|
private let toolbar = UIToolbar()
|
|
private let progressView = UIProgressView(progressViewStyle: .bar)
|
|
private var progressObservation: NSKeyValueObservation?
|
|
private var backButton: UIBarButtonItem!
|
|
private var forwardButton: UIBarButtonItem!
|
|
private var refreshButton: UIBarButtonItem!
|
|
private var adCleanupTimer: Timer?
|
|
|
|
init(url: String, title: String?) {
|
|
urlString = url
|
|
pageTitle = title
|
|
super.init(nibName: nil, bundle: nil)
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
view.backgroundColor = .systemBackground
|
|
title = pageTitle?.isEmpty == false ? pageTitle : I18n.shared.t(key: "web_page")
|
|
navigationItem.leftBarButtonItem = UIBarButtonItem(
|
|
barButtonSystemItem: .close,
|
|
target: self,
|
|
action: #selector(closeTapped)
|
|
)
|
|
setupToolbar()
|
|
setupProgressView()
|
|
|
|
let config = WKWebViewConfiguration()
|
|
let preferences = WKWebpagePreferences()
|
|
preferences.allowsContentJavaScript = true
|
|
config.defaultWebpagePreferences = preferences
|
|
|
|
WebViewContentBlocker.configure(config, pageUrl: urlString) { [weak self] in
|
|
guard let self else { return }
|
|
let webView = WKWebView(frame: .zero, configuration: config)
|
|
webView.navigationDelegate = self
|
|
webView.allowsBackForwardNavigationGestures = true
|
|
webView.translatesAutoresizingMaskIntoConstraints = false
|
|
self.view.addSubview(webView)
|
|
NSLayoutConstraint.activate([
|
|
webView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
|
|
webView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
|
|
webView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
|
|
webView.bottomAnchor.constraint(equalTo: self.toolbar.topAnchor),
|
|
])
|
|
self.webView = webView
|
|
self.view.bringSubviewToFront(self.progressView)
|
|
self.observeProgress(webView)
|
|
self.updateNavigationButtons()
|
|
self.startAdCleanupTimer()
|
|
guard let url = Self.resolveURL(self.urlString) else { return }
|
|
webView.load(URLRequest(url: url))
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
adCleanupTimer?.invalidate()
|
|
progressObservation?.invalidate()
|
|
}
|
|
|
|
private func startAdCleanupTimer() {
|
|
adCleanupTimer?.invalidate()
|
|
adCleanupTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
|
|
self?.webView?.evaluateJavaScript(WebViewAdBlock.shared.domCleanupScript(), completionHandler: nil)
|
|
}
|
|
}
|
|
|
|
private func setupToolbar() {
|
|
toolbar.translatesAutoresizingMaskIntoConstraints = false
|
|
view.addSubview(toolbar)
|
|
|
|
backButton = UIBarButtonItem(
|
|
image: UIImage(systemName: "chevron.backward"),
|
|
style: .plain,
|
|
target: self,
|
|
action: #selector(goBackTapped)
|
|
)
|
|
forwardButton = UIBarButtonItem(
|
|
image: UIImage(systemName: "chevron.forward"),
|
|
style: .plain,
|
|
target: self,
|
|
action: #selector(goForwardTapped)
|
|
)
|
|
refreshButton = UIBarButtonItem(
|
|
barButtonSystemItem: .refresh,
|
|
target: self,
|
|
action: #selector(refreshTapped)
|
|
)
|
|
let flex = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
|
|
toolbar.items = [backButton, flex, forwardButton, flex, refreshButton]
|
|
|
|
NSLayoutConstraint.activate([
|
|
toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
toolbar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
|
|
])
|
|
}
|
|
|
|
private func setupProgressView() {
|
|
progressView.progressViewStyle = .bar
|
|
progressView.progressTintColor = .systemBlue
|
|
progressView.trackTintColor = .clear
|
|
progressView.progress = 0
|
|
progressView.isHidden = true
|
|
progressView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let host: UIView = navigationController?.navigationBar ?? view
|
|
host.addSubview(progressView)
|
|
NSLayoutConstraint.activate([
|
|
progressView.leadingAnchor.constraint(equalTo: host.leadingAnchor),
|
|
progressView.trailingAnchor.constraint(equalTo: host.trailingAnchor),
|
|
progressView.bottomAnchor.constraint(equalTo: host.bottomAnchor),
|
|
progressView.heightAnchor.constraint(equalToConstant: 3),
|
|
])
|
|
}
|
|
|
|
private func observeProgress(_ webView: WKWebView) {
|
|
progressObservation = webView.observe(\.estimatedProgress, options: [.new]) { [weak self] webView, _ in
|
|
DispatchQueue.main.async {
|
|
guard let self else { return }
|
|
let progress = Float(webView.estimatedProgress)
|
|
self.progressView.setProgress(progress, animated: true)
|
|
self.progressView.isHidden = progress <= 0 || progress >= 1
|
|
}
|
|
}
|
|
}
|
|
|
|
private func updateNavigationButtons() {
|
|
guard let webView else { return }
|
|
backButton.isEnabled = webView.canGoBack
|
|
forwardButton.isEnabled = webView.canGoForward
|
|
}
|
|
|
|
@objc private func closeTapped() {
|
|
dismiss(animated: true)
|
|
}
|
|
|
|
@objc private func goBackTapped() {
|
|
webView?.goBack()
|
|
}
|
|
|
|
@objc private func goForwardTapped() {
|
|
webView?.goForward()
|
|
}
|
|
|
|
@objc private func refreshTapped() {
|
|
webView?.reload()
|
|
}
|
|
|
|
private static func resolveURL(_ raw: String) -> URL? {
|
|
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !trimmed.isEmpty else { return nil }
|
|
if let url = URL(string: trimmed) { return url }
|
|
if let encoded = trimmed.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed) {
|
|
return URL(string: encoded)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
extension InAppWebViewController: WKNavigationDelegate {
|
|
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
|
|
progressView.progress = 0
|
|
progressView.isHidden = false
|
|
updateNavigationButtons()
|
|
}
|
|
|
|
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
progressView.isHidden = true
|
|
updateNavigationButtons()
|
|
webView.evaluateJavaScript(WebViewAdBlock.shared.domCleanupScript(), completionHandler: nil)
|
|
}
|
|
|
|
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
|
|
progressView.isHidden = true
|
|
updateNavigationButtons()
|
|
}
|
|
|
|
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
|
|
progressView.isHidden = true
|
|
updateNavigationButtons()
|
|
}
|
|
}
|