Reputation: 2783
var body: some View {
NavigationView {
ZStack {
Color(UIColor.systemGroupedBackground).edgesIgnoringSafeArea(.all)
ScrollView(showsIndicators: false) {
if #available(iOS 15.0, *) {
VStack {
if self.viewModel.forms.isEmpty && !self.viewModel.isLoading {
Text("No Forms Assigned")
.foregroundColor(Color.gray)
.padding(.vertical, 20)
.accessibility(label: Text("No forms assigned"))
}
if self.viewModel.isLoading {
ActivityIndicator(isAnimating: .constant(true), style: .large).padding(.top, 20)
}
noFormsScrollView
Spacer(minLength: 16).accessibility(hidden: true)
}
.refreshable {
self.refreshData()
}
} else {
// Fallback on earlier versions
}
}
}.navigationBarTitle("Forms", displayMode: .inline)
}
}
I am trying to add pull to refresh on my ScrollView
But it's not not working. I am wondering, what i am doing wrong.
Upvotes: 5
Views: 20778
Reputation: 1
Some time ago I've created this small package. It's pretty simple to use just create ScrollView and inside of it add ScrollViewRefresher {}. I hope it helps.
Here is a sample showing how to use it:
struct Content: View{
var body: some View{
ScrollView{
VStack{
ScrollViewRefresher(action: action)
ForEach(0..<10, id: \.self){item in
Text("Item: \(item)")
}
}
}
}
func action() async{
//TODO: Implement async action that will refresh the view, update refreshing once finished
do{
// Delay the task to simulate API call or some other action
try? await Task.sleep(for: .seconds(2))
} catch {
// TODO: Handle error
print(error.localizedDescription)
}
}
}
https://github.com/wegosh/ScrollViewRefresher - sample usage in repo description
Upvotes: 0
Reputation: 41
in my project I needed some functions from UIScrollView, so I created this. It allows you to read scrollview offset, dragging end and define on refresh action. It's little bit hacky.. It extract UIScrollView from SwiftUI ScrollView:
import SwiftUI
/// Extracting UIScrollview from SwiftUI ScrollView for monitoring offset and velocity and refreshing
public struct ScrollDetector: UIViewRepresentable {
public init(
onScroll: @escaping (CGFloat) -> Void,
onDraggingEnd: @escaping (CGFloat, CGFloat) -> Void,
onRefresh: @escaping () -> Void
) {
self.onScroll = onScroll
self.onDraggingEnd = onDraggingEnd
self.onRefresh = onRefresh
}
/// ScrollView Delegate Methods
public class Coordinator: NSObject, UIScrollViewDelegate {
init(parent: ScrollDetector) {
self.parent = parent
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
parent.onScroll(scrollView.contentOffset.y)
}
public func scrollViewWillEndDragging(
_: UIScrollView,
withVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>
) {
parent.onDraggingEnd(targetContentOffset.pointee.y, velocity.y)
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let velocity = scrollView.panGestureRecognizer.velocity(in: scrollView.panGestureRecognizer.view)
parent.onDraggingEnd(scrollView.contentOffset.y, velocity.y)
}
@objc func handleRefresh() {
parent.onRefresh()
refreshControl?.endRefreshing()
}
var parent: ScrollDetector
/// One time Delegate Initialization
var isDelegateAdded: Bool = false
var refreshControl: UIRefreshControl?
}
public var onScroll: (CGFloat) -> Void
/// Offset, Velocity
public var onDraggingEnd: (CGFloat, CGFloat) -> Void
public var onRefresh: () -> Void
public func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
public func makeUIView(context _: Context) -> UIView {
return UIView()
}
public func updateUIView(_ uiView: UIView, context: Context) {
DispatchQueue.main.async {
/// Adding Delegate for only one time
/// uiView - Background
/// .superview = background {}
/// .superview = VStack {}
/// .superview = ScrollView {}
if let scrollview = uiView.superview?.superview?.superview as? UIScrollView, !context.coordinator.isDelegateAdded {
/// Adding Delegate
scrollview.delegate = context.coordinator
context.coordinator.isDelegateAdded = true
/// Adding refresh control
let refreshControl = UIRefreshControl()
refreshControl.addTarget(context.coordinator, action: #selector(Coordinator.handleRefresh), for: .valueChanged)
scrollview.refreshControl = refreshControl
context.coordinator.refreshControl = refreshControl
}
}
}
}
How to use it in SwiftUI:
ScrollView(.vertical, showsIndicators: false) {
VStack {
// content here
}
.background {
ScrollDetector(onScroll: { _ in }, onDraggingEnd: { _, _ in }, onRefresh: {
print("Refresh action")
})
}
}
Upvotes: 1
Reputation: 555
Looks like in iOS 16+ you could do:
ScrollView {
// view body
}
.refreshable {
// refresh action
}
source: https://developer.apple.com/documentation/swiftui/refreshaction https://developer.apple.com/forums/thread/707510
The sources do say that this thing is 15+, but it does not seem to work on 15, even though it does not complain at compile time.
Upvotes: 9
Reputation: 2457
Complete SwiftUI Example
Simply create RefreshableScrollView
in your project
public struct RefreshableScrollView<Content: View>: View {
var content: Content
var onRefresh: () -> Void
public init(content: @escaping () -> Content, onRefresh: @escaping () -> Void) {
self.content = content()
self.onRefresh = onRefresh
}
public var body: some View {
List {
content
.listRowSeparatorTint(.clear)
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
.listStyle(.plain)
.refreshable {
onRefresh()
}
}
}
then use RefreshableScrollView
anywhere in your project
example:
RefreshableScrollView{
// Your content
} onRefresh: {
// do something you want
}
Upvotes: 0
Reputation: 733
Pull to refresh is only automatically available on List
when using refreshable. To make any other view refreshable, you will need to write your own handler for that.
This StackOverflow example shows how to make pull to refresh work on a ScrollView.
Upvotes: 4