You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 07:50:29