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

SwiftUI ScrollView触底检测异常,求修复方案

SwiftUI ScrollView触底加载优化方案

原代码的问题在于给每个列表项都添加了GeometryReader监听位置变化,滚动时所有可见项的frame更新都会触发onChange,再加上判断条件newFrame.minY < scrollViewHeight过于宽泛,导致启动和滑动时大量触发打印。

优化方案

  • 只在ScrollView底部添加一个专用的触底检测视图,避免每个item都监听
  • 使用更精确的触底判断逻辑:检测底部视图是否进入ScrollView的可见范围
  • 添加加载状态控制,避免同一时间多次触发加载

修改后的完整代码

import SwiftUI

struct FeedView: View {
    @StateObject private var viewModel = FeedViewModel()
    @State private var isLoading = false
    @Namespace private var scrollViewNamespace
    
    var body: some View {
        mainInterfaceView
    }
}

extension FeedView {
    var mainInterfaceView: some View {
        ScrollView {
            LazyVStack {
                ForEach(viewModel.tweets) { tweet in
                    Button(action: {}) {
                        someView()
                    }
                }
                
                // 底部触底检测视图
                Color.clear
                    .frame(height: 1)
                    .background(
                        GeometryReader { proxy in
                            Color.clear
                                .onAppear {
                                    checkIfReachedBottom(proxy: proxy)
                                }
                                .onChange(of: proxy.frame(in: .named(scrollViewNamespace))) { _ in
                                    checkIfReachedBottom(proxy: proxy)
                                }
                        }
                    )
            }
        }
        .coordinateSpace(name: scrollViewNamespace)
    }
    
    private func checkIfReachedBottom(proxy: GeometryProxy) {
        // 避免重复加载
        guard !isLoading else { return }
        
        let scrollViewFrame = proxy.frame(in: .named(scrollViewNamespace))
        // 判断检测视图是否进入屏幕可见区域(底部进入即视为触底)
        if scrollViewFrame.minY <= UIScreen.main.bounds.height {
            isLoading = true
            print("called")
            
            // 模拟加载数据,加载完成后重置isLoading
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                viewModel.loadMoreTweets()
                isLoading = false
            }
        }
    }
    
    // 替换成你的实际视图
    private func someView() -> some View {
        Text("Tweet Content")
            .padding()
            .frame(maxWidth: .infinity)
            .background(Color.gray.opacity(0.1))
            .cornerRadius(8)
    }
}

// 示例ViewModel
class FeedViewModel: ObservableObject {
    @Published var tweets: [Tweet] = (0..<20).map { Tweet(id: $0) }
    
    func loadMoreTweets() {
        let newTweets = (tweets.count..<tweets.count+10).map { Tweet(id: $0) }
        tweets.append(contentsOf: newTweets)
    }
}

// 示例Tweet模型
struct Tweet: Identifiable {
    let id: Int
}

关键说明

  • 底部的透明视图仅作为触底检测器,只有它的位置变化会触发监听,大幅减少不必要的回调
  • isLoading状态变量防止同一时间多次触发加载,避免重复打印"called"
  • 判断逻辑scrollViewFrame.minY <= UIScreen.main.bounds.height确保只有当底部视图进入屏幕可见范围时才触发,你也可以根据需求调整阈值(比如提前100px触发加载)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 08:56:06