Skip to content

Instantly share code, notes, and snippets.

@Iron-Ham
Last active January 12, 2022 06:30
Show Gist options
  • Save Iron-Ham/dd6945ca33ec5b6adfc80f14a826233e to your computer and use it in GitHub Desktop.
Save Iron-Ham/dd6945ca33ec5b6adfc80f14a826233e to your computer and use it in GitHub Desktop.
import SwiftUI
public struct RefreshableScrollView<Content, Value>: View where Content: View {
private let content: () -> Content
private let action: () async -> Value
init(@ViewBuilder content: @escaping () -> Content, action: @escaping () async -> Value) {
self.content = content
self.action = action
}
@State var startY: Double = Double.greatestFiniteMagnitude
@State var isLoading = false
@State var value: Value?
public var body: some View {
ScrollView {
GeometryReader { geometry in
HStack {
Spacer()
ProgressView()
.opacity(calculate(offset: geometry.frame(in: .global).minY))
.padding(.top, -30)
.transition(.move(edge: .top))
.onChange(of: geometry.frame(in: .global), perform: { frame in
if !hasReachedThreshold(frame.minY) && isLoading && value != nil {
isLoading = false
return
}
guard hasReachedThreshold(frame.minY), !isLoading else { return }
self.isLoading = true
let feedback = UIImpactFeedbackGenerator(style: .light)
feedback.prepare()
feedback.impactOccurred()
Task.init(priority: .userInitiated, operation: {
self.value = await action()
})
})
Spacer()
}
.onAppear {
startY = geometry.frame(in: .global).minY
}
}
content()
}
}
private func hasReachedThreshold(_ offset: CGFloat) -> Bool {
calculate(offset: offset) == 1
}
private func calculate(offset: CGFloat) -> CGFloat {
min((offset - startY) / 30 + 1, 1.0)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment