SwiftUI WebView下拉刷新功能实现方案求助
SwiftUI WebView下拉刷新无法结束的问题修复
我正在为SwiftUI的WebView实现下拉刷新机制,目前的代码已经接近目标,但存在一个问题:触发刷新后,活动指示器会持续显示,不会自动结束。以下是我当前的实现代码:
import SwiftUI import WebKit struct WebView: UIViewRepresentable { let webView: WKWebView init() { webView = WKWebView(frame: .zero) webView.load(URLRequest(url: URL(string: "https://cnn.com")!)) webView.setPullToRefresh(type: .embed) } func makeUIView(context: Context) -> WKWebView { webView } func updateUIView(_ uiView: WKWebView, context: Context) {} } extension WKWebView { var refreshControl: UIRefreshControl? { (scrollView.getAllSubviews() as [UIRefreshControl]).first } enum PullToRefreshType { case none case embed } func setPullToRefresh(type: PullToRefreshType) { (scrollView.getAllSubviews() as [UIRefreshControl]).forEach { $0.removeFromSuperview() } switch type { case .none: break case .embed: _setPullToRefresh(target: self, selector: #selector(webViewPullToRefreshHandler(source:))) } } private func _setPullToRefresh(target: Any, selector: Selector) { let refreshControl = UIRefreshControl() refreshControl.addTarget(target, action: selector, for: .valueChanged) scrollView.addSubview(refreshControl) } @objc func webViewPullToRefreshHandler(source: UIRefreshControl) { guard let url = self.url else { source.endRefreshing(); return } load(URLRequest(url: url)) } } extension UIView { class func getAllSubviews<T: UIView>(from parenView: UIView) -> [T] { return parenView.subviews.flatMap { subView -> [T] in var result = getAllSubviews(from: subView) as [T] if let view = subView as? T { result.append(view) } return result } } func getAllSubviews<T: UIView>() -> [T] { return UIView.getAllSubviews(from: self) as [T] } }
问题原因
当前代码只触发了页面重新加载,但没有监听页面加载完成的状态,导致UIRefreshControl无法收到结束刷新的信号,所以指示器会一直显示。
修复方案
需要给WKWebView设置导航代理,在页面加载完成或加载失败时,主动结束刷新控件的动画。具体修改如下:
- 在
WebView结构体中添加Coordinator,作为WKNavigationDelegate的实现者 - 在初始化WKWebView时设置代理为Coordinator
- 在Coordinator的代理方法中,调用刷新控件的
endRefreshing()方法
修改后的完整代码
import SwiftUI import WebKit struct WebView: UIViewRepresentable { let webView: WKWebView private let coordinator: Coordinator init() { coordinator = Coordinator() webView = WKWebView(frame: .zero) webView.navigationDelegate = coordinator webView.load(URLRequest(url: URL(string: "https://cnn.com")!)) webView.setPullToRefresh(type: .embed) // 将webView关联到coordinator,方便代理方法中访问 coordinator.webView = webView } func makeUIView(context: Context) -> WKWebView { webView } func updateUIView(_ uiView: WKWebView, context: Context) {} class Coordinator: NSObject, WKNavigationDelegate { weak var webView: WKWebView? func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { webView.refreshControl?.endRefreshing() } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { webView.refreshControl?.endRefreshing() } func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { webView.refreshControl?.endRefreshing() } } } extension WKWebView { var refreshControl: UIRefreshControl? { (scrollView.getAllSubviews() as [UIRefreshControl]).first } enum PullToRefreshType { case none case embed } func setPullToRefresh(type: PullToRefreshType) { (scrollView.getAllSubviews() as [UIRefreshControl]).forEach { $0.removeFromSuperview() } switch type { case .none: break case .embed: _setPullToRefresh(target: self, selector: #selector(webViewPullToRefreshHandler(source:))) } } private func _setPullToRefresh(target: Any, selector: Selector) { let refreshControl = UIRefreshControl() refreshControl.addTarget(target, action: selector, for: .valueChanged) scrollView.addSubview(refreshControl) } @objc func webViewPullToRefreshHandler(source: UIRefreshControl) { guard let url = self.url else { source.endRefreshing(); return } load(URLRequest(url: url)) } } extension UIView { class func getAllSubviews<T: UIView>(from parenView: UIView) -> [T] { return parenView.subviews.flatMap { subView -> [T] in var result = getAllSubviews(from: subView) as [T] if let view = subView as? T { result.append(view) } return result } } func getAllSubviews<T: UIView>() -> [T] { return UIView.getAllSubviews(from: self) as [T] } }
说明
- 添加了
Coordinator类实现WKNavigationDelegate,监听页面加载的完成、失败事件,在这些事件触发时结束刷新动画 - 将
webView弱引用到Coordinator中,避免循环引用 - 覆盖了三种可能的加载结束场景:加载完成、加载失败、初始加载失败,确保无论哪种情况都能结束刷新
内容的提问来源于stack exchange,提问作者Krzysztof P
相关产品推荐
相关产品推荐

