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

TableView下拉加载:保持下拉状态并显示加载指示器实现方案咨询

TableView下拉刷新保持状态并显示加载Spinner的最优实现

针对你说的需求——下拉TableView后保持展开的空白区域并显示加载Spinner,这里有两种实用的实现方案,根据你的场景选择即可:

方案一:定制系统UIRefreshControl(iOS原生场景首选)

系统自带的UIRefreshControl默认会在刷新开始后自动收起,我们只要修改它的行为,再替换指示器就能搞定:

  • 写个UIRefreshControl的子类,重写endRefreshing方法,不让它自动收起,留到数据加载完再手动触发。
  • 把系统默认的指示器换成你需要的Spinner,直接用系统的UIActivityIndicatorView也能满足需求。
  • 开始刷新时手动固定TableView的contentOffset,保持下拉状态,等数据加载完再恢复。

代码示例(Swift):

class CustomRefreshControl: UIRefreshControl {
    // 重写方法,默认不自动收起
    override func endRefreshing() {}
    
    // 提供外部调用的结束方法,手动收起
    func finishRefreshing() {
        super.endRefreshing()
    }
}

// 在ViewController里的用法
let refreshControl = CustomRefreshControl()
refreshControl.addTarget(self, action: #selector(refreshData), for: .valueChanged)
tableView.refreshControl = refreshControl

@objc private func refreshData() {
    // 记录当前下拉的位置,固定住
    let currentOffset = tableView.contentOffset
    tableView.contentOffset = currentOffset
    
    // 添加自定义Spinner到刷新控件里
    let spinner = UIActivityIndicatorView(style: .large)
    spinner.center = refreshControl.center
    spinner.startAnimating()
    refreshControl.addSubview(spinner)
    
    // 模拟数据请求
    DispatchQueue.global().async {
        // 这里写你的数据获取逻辑
        DispatchQueue.main.async {
            // 更新TableView数据
            self.tableView.reloadData()
            // 完成刷新,恢复状态
            self.refreshControl.finishRefreshing()
            spinner.removeFromSuperview()
        }
    }
}

方案二:自定义TableView Header(高度自定义场景)

如果系统控件满足不了你的UI需求(比如要做特殊的下拉动画),直接自定义一个HeaderView更灵活:

  • 做个包含Spinner的HeaderView,初始高度设为0,作为TableView的tableHeaderView。
  • 监听TableView的滚动代理,根据下拉的偏移量调整Header的高度,下拉到指定阈值时触发刷新。
  • 刷新期间固定Header的高度,保持空白区域显示Spinner,加载完再把Header缩回去。

代码示例(Swift):

class CustomRefreshHeader: UIView {
    let spinner = UIActivityIndicatorView(style: .large)
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        addSubview(spinner)
        spinner.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            spinner.centerXAnchor.constraint(equalTo: centerXAnchor),
            spinner.centerYAnchor.constraint(equalTo: centerYAnchor)
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

// ViewController里的实现
private let refreshThreshold: CGFloat = 60 // 触发刷新的下拉距离
private var refreshHeader = CustomRefreshHeader(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 0))
private var isRefreshing = false

override func viewDidLoad() {
    super.viewDidLoad()
    tableView.tableHeaderView = refreshHeader
    tableView.delegate = self
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    guard !isRefreshing else { return }
    
    let pullDistance = -scrollView.contentOffset.y
    // 跟着下拉距离调整Header高度
    refreshHeader.frame.size.height = max(0, pullDistance)
    
    // 下拉到阈值就触发刷新
    if pullDistance >= refreshThreshold {
        startRefreshing()
    }
}

private func startRefreshing() {
    isRefreshing = true
    // 固定Header高度,保持下拉状态
    refreshHeader.frame.size.height = refreshThreshold
    refreshHeader.spinner.startAnimating()
    
    // 数据请求逻辑
    DispatchQueue.global().async {
        // 获取数据
        DispatchQueue.main.async {
            self.tableView.reloadData()
            self.endRefreshing()
        }
    }
}

private func endRefreshing() {
    isRefreshing = false
    refreshHeader.spinner.stopAnimating()
    // 动画缩回头部,恢复初始状态
    UIView.animate(withDuration: 0.3) {
        self.refreshHeader.frame.size.height = 0
        self.tableView.contentOffset = .zero
    }
}

最优选择建议

  • 如果是常规iOS项目,优先用定制系统UIRefreshControl,原生集成的交互逻辑更稳定,不用自己处理复杂的滚动计算,兼容性也更好。
  • 如果需要高度自定义的UI或者特殊的下拉过渡效果,就选自定义HeaderView的方式,灵活性拉满。

内容的提问来源于stack exchange,提问作者Rik

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 15:10:34