可自定义的本机 SwiftUI 刷新控件

复习

适用于 iOS 14+ 的可自定义原生 Swift UI 刷新控件

为什么?

  • 原生 SwiftUI 刷新控件仅适用于 iOS 15+
  • 本机 UIKit 刷新控件适用于丑陋的包装器,但在导航视图中存在错误行为
  • 我需要一个可以容纳叠加层的刷新控件(例如出现在静态图像的顶部)
  • 这个是非常可定制的

查看实际效果

如果您想在真实的应用程序中查看它,请查看 dateit

用法

首先将包添加到项目中。

import Refresher 

struct DetailsView: View {
    @State var refreshed = 0

    var body: some View {
        ScrollView {
            Text("Details!")
            Text("Refreshed: \(refreshed)")
        }
        .refresher { done in // Called when pulled to refresh
            DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
                refreshed += 1
                done() // Stops the refresh view (can be called on a background thread)
            }
        }
    }
}

See: Examples for a full sample project with multiple implementations

Navigation view

Navigation

Refresher plays nice with both Navigation views and navigation subviews.

Subview

Detail view with overlay

Refresher supports an overlay mode to show a refresh indicator over fixed position content

.refresher(overlay: true)

Overlay

System style

Refresher‘s default animation is designed to be more flexible that the system animation style. If you want to behave more like they system refresh control, you can change the style:Refresher

.refresher(style: .system) { done in

System

Customization

Refresher can take a custom spinner view. Your custom view will get a binding instances of the refresher state that contains useful properties for managing animations and translations. Here is a custom spinner that shows an emoji:

public struct EmojiRefreshView: View {
    @Binding var state: RefresherState
    @State private var angle: Double = 0.0
    @State private var isAnimating = false
    
    var foreverAnimation: Animation {
        Animation.linear(duration: 1.0)
            .repeatForever(autoreverses: false)
    }
    
    public var body: some View {
        VStack {
            switch state.mode {
            case .notRefreshing:
                Text("🤪")
                    .onAppear {
                        isAnimating = false
                    }
            case .pulling:
                Text("😯")
                    .rotationEffect(.degrees(360 * state.dragPosition))
            case .refreshing:
                Text("😂")
                    .rotationEffect(.degrees(self.isAnimating ? 360.0 : 0.0))
                        .onAppear {
                            withAnimation(foreverAnimation) {
                                isAnimating = true
                            }
                    }
            }
        }
        .scaleEffect(2)
    }
}

Add the custom refresherView:

.refresher(refreshView: EmojiRefreshView.init ) { done in

Custom

Want to help?

Here are some TODO items

  • Support ListView
  • don’t trigger refresh until drag is released (how do you detect in a scrollview?)touchDown
  • expose the background padding view for customization

GitHub

点击跳转