tHatpart
tHatpart

Reputation: 1146

SwiftUI Refreshable With Custom Loading View

I have a refreshable Scroll View using .refreshable and it works exactly how I need it to except for the loading view. How can I hide or replace that loading wheel?

    .refreshable {
        Task {
           // Reload
        }
    }

enter image description here

Upvotes: 5

Views: 3742

Answers (1)

2poy
2poy

Reputation: 126

I created extension for ScrollView to mimic native refreshable behaviour.

Example of usage:

    ScrollView {
        VStack {
            ForEach(0...100, id: \.self) { index in
                Text("row \(index)")
                    .padding()
                    .frame(width: .infinity)
            }
            
        }
        .frame(maxWidth: .infinity)
    }
    .refreshable {
        // asyncronously refresh your data here
        try? await Task.sleep(nanoseconds: 2_000_000_000)
    }

Extension implementation:

public extension ScrollView {
    func refreshable(onRefresh: @escaping RefreshAction) -> some View {
        ScrollWithRefreshView(content: { self },
                              onRefresh: onRefresh)
    }
}

Custom ScrollView implementation to use in the extension. Don't forget to replace CustomRefreshView with your custom refresh view:

import SwiftUI

public typealias RefreshAction = () async -> ()

struct RefreshViewOffsetKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value += nextValue()
    }
}

public struct ScrollWithRefreshView<Content: View>: View {
    
    let content: Content
    let onRefresh: RefreshAction
    @State private var isRefreshing: Bool = false

    public init(@ViewBuilder content: () -> Content,
                onRefresh: @escaping RefreshAction) {
        self.onRefresh = onRefresh
        self.content = content()
    }
    
    private let amountToPullBeforeRefreshing: CGFloat = 180
    
    public var body: some View {
        ScrollView {
            if isRefreshing {
                
                // PUT YOUR CUSTOM VIEW HERE
                CustomRefreshView()

            }
            content
            .overlay(GeometryReader { geo in
                let currentScrollViewPosition = -geo.frame(in: .global).origin.y
                if currentScrollViewPosition < -amountToPullBeforeRefreshing && !isRefreshing {
                    Color.clear.preference(key: RefreshViewOffsetKey.self, value: -geo.frame(in: .global).origin.y)
                }
            })
        }
        .onPreferenceChange(RefreshViewOffsetKey.self) { scrollPosition in
            if scrollPosition < -amountToPullBeforeRefreshing && !isRefreshing {
                isRefreshing = true
                Task {
                    await onRefresh()
                    isRefreshing = false
                }
            }
        }
    }
}

Upvotes: 1

Related Questions