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
相关产品推荐
相关产品推荐

