Created
June 13, 2025 15:22
-
-
Save CraigSiemens/273a9b7376bc882b383aa62764ee8bae to your computer and use it in GitHub Desktop.
A view modifier that allows `.refreshable` to work reliably alongside state updates that would otherwise cause the refresh task to be cancelled.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extension View { | |
/// A view modifier that allows `.refreshable` to work reliably alongside state updates | |
/// that would otherwise cause the refresh task to be cancelled. | |
/// | |
/// In SwiftUI, when a state change triggers a view redraw, any ongoing `.refreshable` task is | |
/// cancelled. This can be problematic if your refresh action or scrolling also updates state. | |
/// | |
/// - Important: | |
/// `.refreshable` remains the preferred view modifier. Use this modifier only if your view | |
/// experiences unexpected task cancellations, as a workaround for this specific issue. | |
/// | |
/// - Parameter action: The async action to perform when a refresh is triggered. | |
func refreshableWhileUpdatingState(action: @escaping () async -> Void) -> some View { | |
modifier(RefreshableWhileUpdatingState(action: action)) | |
} | |
} | |
private struct RefreshableWhileUpdatingState: ViewModifier { | |
typealias RefreshId = RefreshableWhileUpdatingStateRefreshId | |
@State private var refreshId: RefreshId? | |
let action: () async -> Void | |
func body(content: Content) -> some View { | |
content | |
.refreshable { | |
await withCheckedContinuation { | |
refreshId = .init(continuation: $0) | |
} | |
} | |
.task(id: refreshId) { | |
guard refreshId != nil else { return } | |
await action() | |
refreshId?.continuation.resume() | |
refreshId = nil | |
} | |
} | |
} | |
private struct RefreshableWhileUpdatingStateRefreshId { | |
private let id: UUID = .init() | |
let continuation: CheckedContinuation<Void, Never> | |
} | |
extension RefreshableWhileUpdatingStateRefreshId: Hashable { | |
static func == (lhs: Self, rhs: Self) -> Bool { | |
lhs.id == rhs.id | |
} | |
func hash(into hasher: inout Hasher) { | |
hasher.combine(id) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment