5

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

1

1 Answer 1

1

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
                }
            }
        }
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

it works but has significant lag

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.