Swiftyn LogoSwiftyn
LearnInterview PrepRoadmapsArchitect Profile
Swift LanguageSwiftUIUIKitiOS ConceptsmacOS

SwiftUI Topics

Introduction to SwiftUISwiftUI App LifecycleViews and ModifiersTextImageShapesSF SymbolsButtonsStacksSpacerDividerScrollViewGroupsSectionsHStackVStackZStackLazyVStackLazyHStackLazyVGridLazyHGridGridGridRowGeometryReaderSafeAreaFrames and AlignmentPaddingOverlay and BackgroundPreferenceKeyAnchor PreferencesCustom Layouts@State@Binding@ObservedObject@StateObject@EnvironmentObject@Environment@PublishedObservableObject@Observable@BindableData Flow in SwiftUITwo Way BindingNavigationStackNavigationSplitViewNavigationPathProgrammatic NavigationTabViewDeep LinkingSheetFullScreenCoverPopoverAlertsCustom AnimationsAsync Await in SwiftUIUIKit Integration
Browse SwiftUI Topics
Introduction to SwiftUISwiftUI App LifecycleViews and ModifiersTextImageShapesSF SymbolsButtonsStacksSpacerDividerScrollViewGroupsSectionsHStackVStackZStackLazyVStackLazyHStackLazyVGridLazyHGridGridGridRowGeometryReaderSafeAreaFrames and AlignmentPaddingOverlay and BackgroundPreferenceKeyAnchor PreferencesCustom Layouts@State@Binding@ObservedObject@StateObject@EnvironmentObject@Environment@PublishedObservableObject@Observable@BindableData Flow in SwiftUITwo Way BindingNavigationStackNavigationSplitViewNavigationPathProgrammatic NavigationTabViewDeep LinkingSheetFullScreenCoverPopoverAlertsCustom AnimationsAsync Await in SwiftUIUIKit Integration
Swiftyn Logo

Swiftyn

The go-to platform for Apple developers. Swift, SwiftUI, and beyond.

Questions? Email us at support@swe180.com

Categories

  • SwiftUI
  • Swift Language
  • Xcode
  • visionOS

Our Products

  • SWE180
  • One Percent Engineer

Resources

  • About
  • RSS Feed
  • Apple Developer

© 2026 Swiftyn. All rights reserved.

Privacy PolicyTerms of Service

Swiftyn is the premier learning platform and developer resource for mastering the Apple ecosystem. Whether you are an aspiring iOS developer looking to learn Swift 6, a macOS engineer diving into advanced system architecture, or an XR pioneer building the future with visionOS, our beautifully crafted tutorials, roadmaps, and interview prep guides have you covered. Built by Apple developers, for Apple developers.

SwiftUI12 min read

@Published in SwiftUI: Mastering State with Automatic View Updates

SwiftUI's @Published property wrapper is a cornerstone of reactive programming, enabling automatic view updates whenever your data changes. This article delves into the mechanics of @Published, demonstrating how to integrate it effectively into your app's architecture for seamless and efficient state management. You'll learn its capabilities, best practices, and potential pitfalls.

Introduction to @Published and Reactive Programming

SwiftUI is a declarative UI framework that thrives on state changes. When your app's data changes, you want your UI to reflect those changes automatically, without manual intervention. This is where reactive programming patterns become incredibly powerful, and @Published is SwiftUI's primary tool for achieving this reactivity.

At its core, @Published is a property wrapper that automatically announces when its value changes. This announcement mechanism is powered by Apple's Combine framework. When a property marked with @Published within an ObservableObject changes, it emits a signal. Any SwiftUI View or other ObservableObject that is observing this ObservableObject will then receive this signal and automatically re-render (or trigger a re-render in the observing view). This elegant system dramatically simplifies state management, moving away from imperative update calls to a more natural, data-driven approach.

Understanding @Published is crucial for any SwiftUI developer, as it underpins much of how data flows through and updates your application's interface. It connects your data models to your views, ensuring a consistent and up-to-date user experience.

How @Published Works: The ObservableObject Connection

To harness the power of @Published, your class must conform to the ObservableObject protocol. This protocol, part of the Combine framework, signals that an object can emit changes. When you declare properties within an ObservableObject using @Published, the ObservableObject automatically gains a publisher named objectWillChange.

Whenever a @Published property's value is set to a new value, the objectWillChange publisher automatically emits a signal before the value changes. SwiftUI views observing an ObservableObject (typically via @StateObject or @ObservedObject) subscribe to this objectWillChange publisher. Upon receiving a signal, the view is invalidated and scheduled for re-rendering, picking up the new value of the @Published property.

It's important to note that @Published works by comparing the new value to the old value. If the new value is the same as the old value (based on Equatable conformance for value types), a change will typically not be emitted. This optimization prevents unnecessary view updates. For reference types, a change is emitted if the identity of the object changes.

swift
import SwiftUI
import Combine

class UserSettings: ObservableObject {
    @Published var username: String = "Guest"
    @Published var isLoggedIn: Bool = false
    @Published var score: Int = 0 {
        didSet {
            // Custom logic can still be added via oberservers like didSet
            print("Score changed to \(score)")
        }
    }
}

struct UserProfileView: View {
    @StateObject var settings = UserSettings()
    
    var body: some View {
        VStack {
            Text("Welcome, \(settings.username)")
                .font(.largeTitle)
            Text("Status: \(settings.isLoggedIn ? "Online" : "Offline")")
                .font(.headline)
            Text("Score: \(settings.score)")
                .font(.subheadline)
            
            Button("Login") {
                settings.username = "SwiftDev"
                settings.isLoggedIn = true
            }
            .padding()

            Button("Increase Score") {
                settings.score += 10
            }
            .padding()
        }
        .onAppear {
            // Demonstrating programmatic changes
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                settings.username = "TimedUser"
                settings.isLoggedIn = true
            }
        }
    }
}

Integrating @Published with SwiftUI Views: @StateObject and @ObservedObject

To make your SwiftUI views react to changes in an ObservableObject's @Published properties, you need to declare instances of that ObservableObject within your view using specific property wrappers:

  1. @StateObject: This property wrapper is used to create and own an instance of an ObservableObject. It ensures that the object persists for the lifetime of the view, even if the view is re-rendered. Use @StateObject when the lifecycle of your ObservableObject is tied directly to the lifecycle of the view that creates it. This is the preferred way to instantiate and observe ObservableObjects within a view.

    Compatibility: iOS 14.0+, macOS 11.0+, tvOS 14.0+, watchOS 7.0+

  2. @ObservedObject: This property wrapper is used when a view receives an instance of an ObservableObject from an external source (e.g., a parent view or an environment object). The view doesn't own the object; it merely observes it. If the object itself is replaced, the view might lose its connection. Use @ObservedObject for child views that need to observe and react to changes from an object managed higher up in the view hierarchy.

    Compatibility: iOS 13.0+, macOS 10.15+, tvOS 13.0+, watchOS 6.0+

The choice between @StateObject and @ObservedObject is crucial for correct state management and avoiding unexpected behavior, especially when objects are passed down the view hierarchy.

swift
import SwiftUI

class DataStore: ObservableObject {
    @Published var items: [String] = []
    
    init() {
        // Simulate fetching data
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.items = ["Apple", "Banana", "Cherry"]
        }
    }
}

struct ParentView: View {
    @StateObject var dataStore = DataStore() // Parent owns the DataStore
    
    var body: some View {
        NavigationView {
            VStack {
                Text("Parent View Items:")
                List(dataStore.items, id: \.self) {
                    Text($0)
                }
                NavigationLink(destination: ChildView(dataStore: dataStore)) {
                    Text("Go to Child View")
                }
                Button("Add Item") {
                    dataStore.items.append("New Item \(dataStore.items.count + 1)")
                }
            }
            .navigationTitle("Parent")
        }
    }
}

struct ChildView: View {
    @ObservedObject var dataStore: DataStore // Child observes the DataStore passed from Parent
    
    var body: some View {
        VStack {
            Text("Child View Items:")
            List(dataStore.items, id: \.self) {
                Text($0)
            }
            Button("Add Item from Child") {
                dataStore.items.append("Item from Child \(dataStore.items.count + 1)")
            }
            .navigationTitle("Child")
        }
    }
}

Best Practices and Considerations when using @Published

While @Published simplifies reactivity, using it effectively requires adherence to certain best practices and an understanding of its limitations:

  • Keep your ObservableObjects focused: Design your ObservableObjects to manage specific, cohesive pieces of state. Avoid creating monolithic objects that manage too many unrelated properties, as this can lead to unnecessary view re-renders.

  • Value vs. Reference Types: @Published works great with value types (structs, enums, basic types like Int, String, Bool). For reference types (classes), @Published will only publish a change if the instance itself is replaced. If you modify a property inside a custom class held by a @Published property, the outer @Published won't detect the change. In such cases, the inner class should also be an ObservableObject with its own @Published properties, and you'd observe it directly, or you'd manually call objectWillChange.send() on the outer ObservableObject.

  • Avoid over-publishing: Be mindful of how frequently @Published properties change. Rapid changes can trigger frequent view updates, potentially impacting performance. Consider techniques like debouncing or throttling if you have properties that update extremely rapidly.

  • Manual objectWillChange.send(): While @Published handles most cases, there might be scenarios where you need to manually trigger a view update because a change isn't automatically detected (e.g., when an internal property of a non-ObservableObject class changes). In such cases, you can manually call objectWillChange.send() from your ObservableObject.

  • Thread Safety: @Published properties should generally be updated on the main thread, especially if they drive UI changes. While Combine itself can operate on any thread, SwiftUI's rendering engine expects updates from the main thread. Incorrect thread usage can lead to UI glitches or crashes. Always dispatch UI-related state changes to DispatchQueue.main.

swift
import SwiftUI
import Combine

class ComplexData: ObservableObject {
    // Inner class is NOT ObservableObject, so changing its properties won't automatically update outer @Published
    class DetailInfo {
        var title: String = "Default"
    }

    @Published var counter: Int = 0
    @Published var info: DetailInfo = DetailInfo()

    func updateTitle(_ newTitle: String) {
        // This will NOT trigger an automatic view update because 'info' reference hasn't changed
        info.title = newTitle
        print("Info title set to: \(info.title)")
        // To force an update, you'd need to manually send objectWillChange or replace the whole object
        // objectWillChange.send()
    }
    
    func replaceInfo(_ newTitle: String) {
        let newInfo = DetailInfo()
        newInfo.title = newTitle
        self.info = newInfo // This assignment WILL trigger a view update because 'info' is replaced
    }
}

struct ComplexDataView: View {
    @StateObject var complexData = ComplexData()

    var body: some View {
        VStack {
            Text("Counter: \(complexData.counter)")
            Text("Info Title: \(complexData.info.title)")

            Button("Increment Counter") {
                complexData.counter += 1 // This will update the view
            }
            .padding()

            Button("Update Title (No Auto Update)") {
                complexData.updateTitle("Updated Manually") // View won't update without manual send()
            }
            .padding()

            Button("Replace Info Object (Will Auto Update)") {
                // This will trigger a view update because @Published 'info' property itself is set to a new instance
                complexData.replaceInfo("Replaced Info Title")
            }
            .padding()
        }
    }
}

Common Pitfalls and How to Avoid Them

Developers new to SwiftUI and @Published often encounter a few common issues:

  • Forgetting ObservableObject: If you use @Published on a property within a class that doesn't conform to ObservableObject, it literally does nothing. Always ensure your class inherits from ObservableObject.

  • @ObservedObject vs. @StateObject misuse: Misunderstanding the ownership semantics of these two wrappers can lead to objects being deallocated prematurely (@ObservedObject used where @StateObject was needed) or views not updating correctly. Remember: @StateObject owns and creates; @ObservedObject receives and observes.

  • Modifying @Published properties on background threads: Although Combine can operate on background threads, UI updates in SwiftUI should always occur on the main thread. If you fetch data on a background thread and then assign it to a @Published property, ensure this assignment happens on DispatchQueue.main to prevent UI anomalies or crashes.

  • Value types vs. reference types and nested changes: As mentioned, @Published on a var myObject: MyClass will only publish changes if myObject is assigned a new instance. Changing properties inside MyClass will not trigger an update unless MyClass is itself an ObservableObject and observed, or you manually trigger objectWillChange.send() on the outer object.

By being aware of these common pitfalls, you can write more robust and predictable SwiftUI applications.

Manual UI Updates

Mastering SwiftUI's Reactive Core: @Published

THE MYTH or PROBLEM: Manual UI Updates

Developers often struggle with updating UI efficiently when backend data changes, leading to imperative calls like `tableView.reloadData()` or manual setter methods, which increases complexity and potential for bugs.

swift
class OldStyleViewController: UIViewController {
    var userName: String = "" // Data
    var nameLabel: UILabel = UILabel()

    func updateUI(with newName: String) {
        self.userName = newName
        self.nameLabel.text = newName // Manual UI update
    }
}

WHAT HAPPENS INTERNALLY? The @Published Flow

`@Published` leverages the Combine framework to automatically notify subscribers (like SwiftUI views) whenever its wrapped value changes. This creates a reactive pipeline from data model to UI.

ObservableObject (e.g., ViewModel)
@Published var property
objectWillChange publisher
SwiftUI View (@StateObject/@ObservedObject)
1

1. Value Assigned

A new value is assigned to a property marked with `@Published` within an `ObservableObject`.

2

2. objectWillChange.send()

The `ObservableObject`'s implicit `objectWillChange` publisher automatically emits a signal *before* the property value actually changes.

3

3. SwiftUI View Subscribes

A SwiftUI `View` using `@StateObject` or `@ObservedObject` has subscribed to this `objectWillChange` publisher.

4

4. View Invalidated

Upon receiving the `objectWillChange` signal, the SwiftUI rendering engine marks the observing view as 'dirty' or 'invalidated'.

5

5. View Re-renders

During the next rendering cycle on the main thread, the invalidated view (and potentially its affected child views) re-renders, picking up the new value of the `@Published` property and updating the UI.

Visualized execution hierarchy.

Powerful Guarantees

Automatic UI Sync

Views automatically reflect the latest data without imperative update calls.

Data Consistency

Helps ensure your UI always matches your underlying data model.

Simplified State Flow

Clean, declarative way to manage data dependencies and propagate changes.

REAL PRODUCTION EXAMPLE: A Chat Application's Message Feed

In a chat app, new messages arrive asynchronously. Without `@Published`, you'd manually append messages and then force the UI to refresh. With `@Published`, the message feed updates automatically and efficiently.

Impact / Results
Real-time UI updates without manual refresh calls
Reduced boilerplate for state synchronization
More robust and less error-prone message display
THE FIX or SOLUTION
swift
import SwiftUI
import Combine

struct Message: Identifiable, Equatable {
    let id = UUID()
    let text: String
    let sender: String
}

class ChatViewModel: ObservableObject {
    @Published var messages: [Message] = [] // This is the magic!
    
    func sendMessage(_ text: String, sender: String) {
        let newMessage = Message(text: text, sender: sender)
        // Appending to this @Published array automatically notifies observing views!
        messages.append(newMessage)
    }
    
    func simulateIncomingMessage(after delay: TimeInterval) {
        DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
            let incoming = Message(text: "Hey, how are you?", sender: "Friend")
            self.messages.append(incoming)
        }
    }
}

struct ChatView: View {
    @StateObject var viewModel = ChatViewModel()
    @State private var newMessageText: String = ""
    
    var body: some View {
        VStack {
            ScrollViewReader {
                proxy in
                List(viewModel.messages) {
                    message in
                    HStack {
                        if message.sender == "Me" {
                            Spacer()
                            Text(message.text)
                                .padding(8)
                                .background(Color.blue)
                                .foregroundColor(.white)
                                .cornerRadius(8)
                        } else {
                            Text(message.text)
                                .padding(8)
                                .background(Color.gray)
                                .foregroundColor(.white)
                                .cornerRadius(8)
                            Spacer()
                        }
                    }
                    .id(message.id) // Ensure ID for ScrollViewReader
                }
                .onChange(of: viewModel.messages.count) { _ in
                    if let lastMessage = viewModel.messages.last {
                        proxy.scrollTo(lastMessage.id, anchor: .bottom)
                    }
                }
            }
            
            HStack {
                TextField("Enter message", text: $newMessageText)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                Button("Send") {
                    viewModel.sendMessage(newMessageText, sender: "Me")
                    newMessageText = ""
                }
                .disabled(newMessageText.isEmpty)
            }
            .padding()
        }
        .navigationTitle("My Chat")
        .onAppear {
            viewModel.simulateIncomingMessage(after: 1.0)
            viewModel.simulateIncomingMessage(after: 3.0)
        }
    }
}

INTERVIEW PERSPECTIVE

Common Question

“Explain how @Published works with ObservableObject and SwiftUI, and when you would choose it over @State.”

Strong Answer

A strong answer explains that `@Published` is a property wrapper for properties within `ObservableObject` classes, leveraging Combine's `objectWillChange` publisher to automatically broadcast changes. SwiftUI views declare instances of `ObservableObject` using `@StateObject` (for ownership) or `@ObservedObject` (for observation), subscribing to these updates and triggering re-renders. I'd choose `@Published` for complex, shared, or asynchronously updated data models (view models) that need to persist across view lifecycles and be observed by multiple views, whereas `@State` is for simple, isolated, view-owned internal state.

Interviewers Expect you to understand:
  • Understanding of ObservableObject and Combine integration
  • Clear distinction between @Published/@StateObject/@ObservedObject vs. @State
  • Knowledge of when and how views re-render
  • Mention of thread-safety considerations (main thread for UI)
KEY TAKEAWAY

`@Published` is SwiftUI's declarative workhorse for reactive state management within `ObservableObject`s, ensuring your UI automatically stays in sync with your data. Use `@StateObject` to own, and `@ObservedObject` to observe, these critical data sources.

Common Interview Questions

When should I use @Published versus @State?

`@State` is for simple, private, ephemeral state owned by a single view. `@Published` is for complex state, often shared across multiple views, that resides within an `ObservableObject` class. `ObservableObject`s act as your data models or view models, while `@State` typically manages UI-specific state like a toggle's `isOn` value.

Can I use @Published with a struct?

No, `@Published` can only be applied to properties within a class that conforms to the `ObservableObject` protocol. Structures are value types and don't support the `ObservableObject` protocol or the Combine-based publishing mechanism that `@Published` relies upon.

What happens if I change a @Published property on a background thread?

If you change a `@Published` property on a background thread, the `objectWillChange` publisher will emit its signal on that background thread. If a SwiftUI `View` is observing this, the subsequent UI update might not happen on the main thread, leading to potential crashes, UI glitches, or undefined behavior. Always ensure that assignments to `@Published` properties that affect the UI occur on the main thread, typically by using `DispatchQueue.main.async`.

Does @Published work with deeply nested objects?

By default, `@Published` only detects changes to the immediate property it wraps. If you have `@Published var item: MyClass` and you modify a property inside `item` (e.g., `item.name = "New Name"`), the outer `@Published` won't trigger an update because the `item` reference itself hasn't changed. For nested observation, `MyClass` would need to be `ObservableObject` itself, and you'd use `@ObservedObject` or `@StateObject` on `item` *within* the view, or manually call `objectWillChange.send()` in the parent `ObservableObject`.

Is @Published thread-safe?

The `@Published` property wrapper itself doesn't guarantee thread safety for its underlying value's modifications from multiple threads simultaneously. While it ensures that changes are announced via Combine, you still need to ensure that modifications to the wrapped value are synchronized if accessed concurrently from multiple threads to prevent data races. For UI updates, as mentioned, always dispatch to the main queue.

#SwiftUI#State Management#Published#Combine#Property Wrappers