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

SwiftUI中如何隐藏或替换.refreshable的默认加载视图?

SwiftUI 自定义或隐藏.refreshable默认加载指示器

你用SwiftUI的.refreshable实现了下拉刷新的ScrollView,功能正常但想换掉或者隐藏默认的加载轮?这里给你两种可行方案:

先贴出你的原代码和默认加载效果:

.refreshable {
    Task {
       // 数据刷新逻辑
    }
}

默认加载效果:
默认下拉加载轮


一、直接隐藏默认加载轮

如果只是想隐藏默认加载轮,可以通过桥接UIKit的UIRefreshControl,将其tintColor设为透明来实现,下面是封装好的自定义ScrollView:

struct NoIndicatorScrollView<Content: View>: UIViewRepresentable {
    let content: Content
    
    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }
    
    func makeUIView(context: Context) -> UIScrollView {
        let scrollView = UIScrollView()
        let refreshControl = UIRefreshControl()
        refreshControl.tintColor = .clear // 隐藏指示器
        scrollView.refreshControl = refreshControl
        
        // 将SwiftUI内容嵌入UIScrollView
        let hostingView = UIHostingController(rootView: content)
        hostingView.view.translatesAutoresizingMaskIntoConstraints = false
        scrollView.addSubview(hostingView.view)
        
        // 设置约束对齐
        NSLayoutConstraint.activate([
            hostingView.view.topAnchor.constraint(equalTo: scrollView.topAnchor),
            hostingView.view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            hostingView.view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            hostingView.view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            hostingView.view.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
        ])
        
        return scrollView
    }
    
    func updateUIView(_ uiView: UIScrollView, context: Context) {
        // 绑定刷新事件
        uiView.refreshControl?.addTarget(context.coordinator, action: #selector(Coordinator.handleRefresh), for: .valueChanged)
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject {
        let parent: NoIndicatorScrollView
        
        init(_ parent: NoIndicatorScrollView) {
            self.parent = parent
        }
        
        @objc func handleRefresh(sender: UIRefreshControl) {
            Task {
                // 这里写你的刷新逻辑
                // 刷新完成后结束刷新状态
                sender.endRefreshing()
            }
        }
    }
}

使用时直接替换原ScrollView即可:

NoIndicatorScrollView {
    VStack {
        ForEach(0..<20) { index in
            Text("Item \(index)")
                .padding()
        }
    }
}

二、替换成自定义加载视图

如果要换成自己设计的加载动画,需要抛弃默认的.refreshable,自己监听ScrollView的下拉偏移量,控制自定义视图的显示状态。下面是完整的封装代码:

struct CustomRefreshScrollView<Content: View>: View {
    let content: Content
    let onRefresh: () async -> Void
    
    @State private var isRefreshing = false
    @State private var offsetY: CGFloat = 0
    
    init(@ViewBuilder content: () -> Content, onRefresh: @escaping () async -> Void) {
        self.content = content()
        self.onRefresh = onRefresh
    }
    
    var body: some View {
        ScrollView {
            VStack {
                // 自定义加载视图,刷新时显示
                if isRefreshing {
                    CustomLoadingView()
                        .frame(height: 60)
                }
                
                content
            }
            .offset(y: isRefreshing ? 0 : -offsetY)
            .background(
                // 监听ScrollView偏移量
                GeometryReader { proxy in
                    Color.clear
                        .preference(key: OffsetKey.self, value: proxy.frame(in: .named("scroll")).minY)
                }
            )
        }
        .coordinateSpace(name: "scroll")
        .onPreferenceChange(OffsetKey.self) { value in
            let triggerThreshold: CGFloat = 80 // 下拉超过此值触发刷新
            offsetY = max(0, -value)
            
            // 满足条件触发刷新
            if offsetY > triggerThreshold && !isRefreshing {
                isRefreshing = true
                Task {
                    await onRefresh()
                    isRefreshing = false
                }
            }
        }
    }
    
    // 自定义加载视图示例:旋转圆环+文字
    private func CustomLoadingView() -> some View {
        HStack(spacing: 10) {
            Circle()
                .trim(from: 0, to: 0.7)
                .stroke(Color.orange, lineWidth: 3)
                .rotationEffect(.degrees(isRefreshing ? 360 : 0))
                .animation(.linear(duration: 1).repeatForever(autoreverses: false), value: isRefreshing)
            
            Text("加载中...")
                .foregroundColor(.orange)
                .font(.subheadline)
        }
    }
}

// 传递偏移量的PreferenceKey
private struct OffsetKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

使用时传入你的内容和刷新逻辑:

CustomRefreshScrollView(onRefresh: {
    // 模拟刷新过程,实际替换为你的数据请求逻辑
    try? await Task.sleep(nanoseconds: 2_000_000_000)
}) {
    VStack {
        ForEach(0..<20) { index in
            Text("自定义刷新 Item \(index)")
                .padding()
                .background(.gray.opacity(0.1))
                .cornerRadius(8)
                .padding(.horizontal)
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 03:35:41