Using CurrentValueSubject in SwiftUI with Combine

|

In SwiftUI, Combine provides a variety of tools to handle asynchronous and reactive data, and CurrentValueSubject is one of the most practical publishers for managing state. Unlike PassthroughSubject, which only broadcasts values when you explicitly send them, CurrentValueSubject maintains a current value and emits that value to new subscribers immediately. This makes it ideal for cases where you need to hold and update a piece of data that multiple parts of your app might observe.

What is CurrentValueSubject?

CurrentValueSubject is a publisher that holds a current value and emits it to any new subscribers. You can update this value at any time, and all subscribers will automatically receive the updated value. It’s commonly used to create state-like behavior in reactive programming, which fits perfectly with SwiftUI’s data-binding approach.

In this article, we’ll create a SwiftUI view that demonstrates how to use CurrentValueSubject to manage and display a count that users can increment by pressing a button.

Example SwiftUI View with CurrentValueSubject

Below is a SwiftUI view that uses CurrentValueSubject to hold and update a counter value, which is displayed in the UI.

import SwiftUI
import Combine

struct CurrentValueSubjectView: View {
    // State variable to display the current count in the view
    @State private var countText: String = "Current Count: 0"
    
    // CurrentValueSubject to hold and update the count
    private var countSubject = CurrentValueSubject<Int, Never>(0)
    
    // AnyCancellable to store the subscription
    @State private var cancellable: AnyCancellable?
    
    var body: some View {
        VStack(spacing: 20) {
            Text("CurrentValueSubject Example")
                .font(.headline)
                .padding()
            
            Text(countText) // Display the current count
                .font(.title)
                .padding()
            
            Button("Increment Count") {
                // Increment the count value in CurrentValueSubject
                countSubject.value += 1
            }
            .padding()
            .background(Color.green)
            .foregroundColor(.white)
            .cornerRadius(8)
        }
        .onAppear {
            // Subscribe to the CurrentValueSubject and update the countText on change
            cancellable = countSubject
                .sink { newCount in
                    countText = "Current Count: \(newCount)"
                }
        }
        .onDisappear {
            // Cancel the subscription when the view disappears
            cancellable?.cancel()
        }
    }
}

struct CurrentValueSubjectView_Previews: PreviewProvider {
    static var previews: some View {
        CurrentValueSubjectView()
    }
}

Explanation of the Code

  1. State Variable:
    • @State private var countText: Holds the string displayed in the view that shows the current count. This variable is updated whenever the CurrentValueSubject emits a new value.
  2. CurrentValueSubject:
    • private var countSubject = CurrentValueSubject<Int, Never>(0): This CurrentValueSubject holds an integer value (starting at 0) and has a Never failure type, meaning it doesn’t produce errors. CurrentValueSubject maintains the current count and allows us to emit updated values whenever the count changes.
  3. Button to Increment the Count:
    • The Increment Count button increments the countSubject‘s value by 1. Since CurrentValueSubject emits its current value immediately to all subscribers, updating value will automatically broadcast the new count to the subscriber.
  4. Subscriber Setup in onAppear:
    • In .onAppear, we subscribe to countSubject with .sink, updating countText each time the CurrentValueSubject emits a new count. This automatically refreshes the view to display the updated count.
  5. Cleaning Up the Subscription:
    • The .onDisappear modifier cancels the subscription when the view disappears, which is a good practice to prevent retaining subscriptions when they’re no longer needed.

Summary

This example shows how CurrentValueSubject can be used as a reactive variable that holds and updates a value over time. Each time the button is pressed, the count increases by one, and CurrentValueSubject automatically notifies all subscribers of the new count. SwiftUI then observes the updated countText, causing the view to re-render with the new count displayed.

Using CurrentValueSubject allows you to manage state reactively, making it easy to handle dynamic data that updates frequently or based on user interactions. This pattern is especially valuable in SwiftUI for managing shared data across views or features that benefit from Combine’s reactive capabilities.

Why not just increment the value?

Here’s why CurrentValueSubject might still be beneficial in certain cases:

  1. Shared State Across Multiple Views: CurrentValueSubject is useful when you need a single source of truth that can be observed by multiple views or components. For example, if you have a shared counter value across different parts of your app, CurrentValueSubject lets each view automatically react to changes in the count without manually passing values around.
  2. Combine-Friendly Architecture: If your app already uses Combine extensively (e.g., for networking, state management, or complex data flows), CurrentValueSubject allows you to handle data changes in a consistent reactive manner. This makes it easier to maintain a cohesive codebase where data is managed through Combine rather than using a mix of reactive and imperative patterns.
  3. Event-Driven Programming: CurrentValueSubject enables a more event-driven approach, which is particularly helpful if you plan to add Combine operators (like map, filter, throttle, etc.) to your publisher. This allows you to process or transform the data in a reactive chain without modifying the original code logic.
  4. External Data or Asynchronous Updates: When the data source might come from something external or asynchronous (e.g., a network call or external service), CurrentValueSubject can hold the current state and broadcast it whenever it updates. For example, a CurrentValueSubject could be updated from a background task and then push new values to the UI.

In simple cases, a direct @State variable may indeed be the best choice. However, as your app grows in complexity, CurrentValueSubject offers flexibility for shared, reactive, and asynchronous data management that scales well with Combine and SwiftUI.

Leave a Reply

Your email address will not be published. Required fields are marked *