Files
LoveLiveMusicPlayerKMP/iosApp/iosApp/InAppWebViewController.swift
T

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()
}
}