Skip to content

Combine and SwiftUI

Husnain Ali edited this page Sep 27, 2026 · 2 revisions

Combine and SwiftUI

Combine (#if canImport(Combine))

client.publisher(for: GetProfile())                            // one value, then completion
client.paginatePublisher(ListUsers())                          // one value per page
client.uploadPublisher(CreatePhoto(), from: .multipart(form))  // .progress(_) ... .finished(Response)
client.downloadPublisher(GetExport(), to: dest)                // .progress(_) ... .finished(URL)

Values arrive on whatever executor the request finished on; call .receive(on:) yourself for UI. Cancelling the subscription cancels the underlying Task.

SwiftUI (iOS 17+, #if canImport(Observation))

@MainActor
struct UsersScreen: View {
    let client: NetworkClient
    @State private var users: NetworkResource<[User]>
    @State private var feed: Paged<Post>

    init(client: NetworkClient) {
        self.client = client
        _users = State(wrappedValue: NetworkResource(client: client))
        _feed = State(wrappedValue: Paged(client: client))
    }

    var body: some View {
        List {
            ForEach(users.value ?? []) { user in Text(user.name) }
            ForEach(feed.items) { post in
                PostRow(post: post)
                    .onAppear { Task { await feed.loadMoreIfNeeded(currentItem: post) } }
            }
            if feed.isLoadingMore { ProgressView() }
        }
        .overlay { if users.isLoading { ProgressView() } }
        .task {
            await users.load(ListUsers())
            await feed.start(FeedPage())
        }
        .refreshable { await users.reload() }
    }
}

NetworkResource<Value> exposes phase (idle / loading / loaded / failed), value, error, isLoading. load(_:) cancels the previous request and keeps the last value visible while reloading. A load that is superseded or cancelled (for example the .task view modifier tearing down) never surfaces as .failed; the previous phase is left in place, and a cancelled first page of Paged never ends pagination either. There is no ObservableObject fallback for iOS 16; an iOS-16 app models this with its own @Observable view models (see the SwiftUI demo).

Clone this wiki locally