{"article":{"slug":"swiftui-data-dependencies-and-their-effect-on-view-updates","title":"SwiftUI data dependencies and their effect on view updates","subtitle":null,"summary":"Natalia Panferova compares stored inputs, state, bindings, environment values, and observable models—and explains which changes actually re-evaluate a SwiftUI view body.","content_type":"tutorial","language":"en","canonical_url":"https://nilcoalescing.com/blog/SwiftUIDataDependenciesAndTheirEffectOnViewUpdates/","author":{"name":"Natalia Panferova","url":"https://nilcoalescing.com/","person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"Nil Coalescing","url":"https://nilcoalescing.com/","listing_slug":null,"listing":null},"topics":[{"name":"SwiftUI","slug":"swiftui","url":"https://listedarticles.com/topics/swiftui"},{"name":"iOS","slug":"ios","url":"https://listedarticles.com/topics/ios"},{"name":"Programming","slug":"programming","url":"https://listedarticles.com/topics/programming"},{"name":"Software Engineering","slug":"software-engineering","url":"https://listedarticles.com/topics/software-engineering"},{"name":"Tutorials","slug":"tutorials","url":"https://listedarticles.com/topics/tutorials"}],"about_listings":[],"cover_image_url":"https://nilcoalescing.com/static/blog/SwiftUIDataDependenciesAndTheirEffectOnViewUpdates/banner.CwRN8gyH-1Yju8vArZls8XmXHgTMFdkwVDhxuoaiQBY.png","license":"all-rights-reserved","word_count":2954,"reading_minutes":13,"published_at":"2026-08-30T12:00:00.000Z","added_at":"2026-09-26T03:13:03.616Z","updated_at":"2026-09-26T03:13:03.616Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/swiftui-data-dependencies-and-their-effect-on-view-updates","markdown_url":"https://listedarticles.com/articles/swiftui-data-dependencies-and-their-effect-on-view-updates.md","example":false,"citation":"Natalia Panferova, Nil Coalescing. \"SwiftUI data dependencies and their effect on view updates.\" 30 Aug 2026. https://nilcoalescing.com/blog/SwiftUIDataDependenciesAndTheirEffectOnViewUpdates/ (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://nilcoalescing.com/blog/SwiftUIDataDependenciesAndTheirEffectOnViewUpdates/"},"body_markdown":"# SwiftUI data dependencies and their effect on view updates\n\nA SwiftUI view can receive the data it needs in many forms. We can pass values through stored properties, keep local state, accept bindings, read values from the environment or use an observable model. These declarations all connect data to a view, but they do not all become dependencies in the same way or have the same effect on view updates.\n\nIn this post, we will compare these different types of data dependencies and see why some changes cause a view's body to run again while others do not. We will also look at cases where a value appears unchanged from the app's perspective but still leads to another body evaluation.\n\n##  #  Plain stored properties \n\nRegular stored properties are the most direct form of data dependency in a SwiftUI view. Values passed when the view is initialized become part of the view value. When a parent produces a new instance of the view, SwiftUI compares its stored inputs with the previous instance to determine whether the body needs to be evaluated again.\n\n###  #  Value types \n\nFor value-type inputs, SwiftUI compares the values stored in one instance of the view with those in the next. This comparison extends through a value's stored properties, so it applies both to types such as `String` and `Int` and to custom structures composed of multiple values.\n\nIn the example below, both `birdName` and `sightingCount` are value-type inputs. If either value changes, SwiftUI reevaluates the `BirdDetailsView` body.\n    \n    \n    struct BirdDetailsView: View {\n        let birdName: String\n        let sightingCount: Int\n    \n        var body: some View {\n            VStack {\n                Image(birdName)\n                Text(\"Bird: \\(birdName)\")\n                Text(\"Number of sightings: \\(sightingCount)\")\n            }\n        }\n    }\n    \n\nPlain stored properties remain part of a view's inputs regardless of whether `body` accesses them. SwiftUI considers all stored values when comparing successive instances of a view. As a result, changing an unused input can still cause the view's `body` to be reevaluated.\n\nFor example, we could move the text that displays the sighting count from `BirdDetailsView` into its parent but forget to remove `sightingCount` from the child view's inputs.\n    \n    \n    struct BirdDetailsView: View {\n        let birdName: String\n    \n        // This property is not used in body, but SwiftUI still compares it\n        // when the parent creates a new BirdDetailsView value.\n        let sightingCount: Int\n    \n        var body: some View {\n            VStack {\n                Image(birdName)\n                Text(\"Bird: \\(birdName)\")\n            }\n        }\n    }\n    \n    struct BirdSightingsView: View {\n        @State private var sightingCount = 0\n    \n        private let birdName = \"Fantail\"\n    \n        var body: some View {\n            VStack {\n                BirdDetailsView(\n                    birdName: birdName,\n                    sightingCount: sightingCount\n                )\n                \n                Text(\"Number of sightings: \\(sightingCount)\")\n                \n                Spacer()\n    \n                Button(\"Add sighting\") {\n                    // The state update produces a new BirdDetailsView value with\n                    // a different sightingCount input, so its body runs again.\n                    sightingCount += 1\n                }\n            }\n            .padding()\n        }\n    }\n    \n\nIncrementing the count will create a `BirdDetailsView` with a different stored value, so its `body` will run again even though it no longer reads `sightingCount`.\n\nWhen passing value types into a view, we should only include the values it needs to produce its content. Otherwise, a change to an unused input can make the view's body run again without affecting the resulting interface.\n\n###  #  Reference types \n\nUnlike value types, reference-type inputs are not compared by examining the values stored on the instance. SwiftUI checks whether the view received the same class instance as before. A regular stored input only appears different when the parent passes another instance.\n\nFor example, we can store the bird data in a plain class and replace the instance when selecting a different bird.\n    \n    \n    final class BirdRecord {\n        var name: String\n        var sightingCount: Int\n    \n        init(name: String, sightingCount: Int) {\n            self.name = name\n            self.sightingCount = sightingCount\n        }\n    }\n    \n    struct BirdDetailsView: View {\n        let bird: BirdRecord\n    \n        var body: some View {\n            VStack {\n                Image(bird.name)\n                Text(\"Bird: \\(bird.name)\")\n                Text(\"Number of sightings: \\(bird.sightingCount)\")\n            }\n        }\n    }\n    \n    struct BirdSightingsView: View {\n        @State private var bird = BirdRecord(\n            name: \"Fantail\",\n            sightingCount: 4\n        )\n    \n        var body: some View {\n            VStack {\n                BirdDetailsView(bird: bird)\n    \n                Spacer()\n    \n                Button(\"Show kea\") {\n                    bird = BirdRecord(\n                        name: \"Kea\",\n                        sightingCount: 2\n                    )\n                }\n            }\n            .padding()\n        }\n    }\n    \n\nPressing the button replaces the stored reference with a new `BirdRecord` instance. `BirdDetailsView` receives a different instance, so SwiftUI reevaluates its body.\n\nChanges made to properties on the existing instance behave differently. Mutating a property does not change the reference stored by the view, so the input still appears unchanged to SwiftUI.\n\nIn the example below, pressing the button updates the properties on the existing `BirdRecord` instead of replacing it with another instance.\n    \n    \n    struct BirdSightingsView: View {\n        @State private var bird = BirdRecord(\n            name: \"Fantail\",\n            sightingCount: 4\n        )\n    \n        var body: some View {\n            VStack {\n                BirdDetailsView(bird: bird)\n    \n                Spacer()\n    \n                Button(\"Show kea\") {\n                    // Mutating these properties keeps the same BirdRecord instance,\n                    // so BirdDetailsView's body does not run.\n                    bird.name = \"Kea\"\n                    bird.sightingCount = 2\n                }\n            }\n            .padding()\n        }\n    }\n    \n\n`BirdDetailsView` continues to display the fantail and its original sighting count because its body does not run after the properties are mutated.\n\nPlain reference types work as view inputs when replacing the entire instance represents a change in the data. For models that change in place, we need an observation mechanism to keep the interface in sync.\n\n###  #  Closures \n\nClosures can also be passed into a view through regular stored properties. Like the value and reference types we discussed, they become part of the view value SwiftUI examines when the parent produces a new instance. The difference is that SwiftUI cannot reliably determine whether two closure values are the same.\n\nWe can see the effect of this comparison behavior when a state change reevaluates a parent's body without changing any data used by one of its subviews. In the example below, `BirdSightingsView` owns a watchlist and passes `BirdDetailsView` a closure for adding the displayed bird to it. It also stores `sightingCount`, which is not passed to `BirdDetailsView`. When `sightingCount` changes, `BirdSightingsView` reevaluates its body and creates the `addToWatchlist` closure again, so SwiftUI treats `BirdDetailsView` as changed and runs its body even though none of the values used to produce its interface have changed.\n    \n    \n    struct BirdDetailsView: View {\n        let birdName: String\n        let addToWatchlist: () -> Void\n    \n        var body: some View {\n            VStack {\n                Image(birdName)\n                Text(\"Bird: \\(birdName)\")\n    \n                Button(\n                    \"Add to watchlist\",\n                    action: addToWatchlist\n                )\n            }\n        }\n    }\n    \n    struct BirdSightingsView: View {\n        @State private var sightingCount = 0\n        @State private var watchlist: Set<String> = []\n    \n        private let birdName = \"Fantail\"\n    \n        var body: some View {\n            VStack {\n                BirdDetailsView(\n                    birdName: birdName,\n                    addToWatchlist: {\n                        watchlist.formUnion([birdName])\n                    }\n                )\n    \n                Text(\"Number of sightings: \\(sightingCount)\")\n                Text(\"Birds in your watchlist: \\(watchlist.count)\")\n    \n                Spacer()\n    \n                Button(\"Add sighting\") {\n                    // Updating sightingCount makes BirdDetailsView's body run again,\n                    // even though it does not change any values used to produce its interface.\n                    sightingCount += 1\n                }\n            }\n            .padding()\n        }\n    }\n    \n\n`BirdDetailsView` produces the same interface after this additional body evaluation because `birdName` has not changed. The cost may be negligible for a view this small, but it can become significant when the parent reevaluates frequently or when the subview performs more work in its body.\n\nWe should avoid passing closures to subviews when possible, and prefer inputs that SwiftUI can compare reliably.\n\n[![SwiftUI Fundamentals by Natalia Panferova book cover](/static/BookBanner/swiftui-fundamentals/Book-cover.3-jxqJTv9SPvQB3r5zzy7drsfc4xuK5M6TsZfXWW5TM.png)![SwiftUI Fundamentals by Natalia Panferova book cover](/static/BookBanner/swiftui-fundamentals/Book-cover-dark.6PcmVP3XX2TVaNt503y99IXaQ108zXnk1osLG4-hM4U.png)Deepen your understanding of SwiftUI!$35The essential guide to SwiftUI core concepts and APIsSwiftUI Fundamentalsby Natalia Panferova\n\n  * Explore the key APIs and design patterns that form the foundation of SwiftUI\n  * Develop a deep, practical understanding of how SwiftUI works under the hood\n  * Learn from a former Apple engineer who worked on widely used SwiftUI APIs\n\nDeepen your understanding of SwiftUI!The essential guide to SwiftUI core concepts and APIs![SwiftUI Fundamentals by Natalia Panferova book cover](/static/BookBanner/swiftui-fundamentals/Book-cover.3-jxqJTv9SPvQB3r5zzy7drsfc4xuK5M6TsZfXWW5TM.png)![SwiftUI Fundamentals by Natalia Panferova book cover](/static/BookBanner/swiftui-fundamentals/Book-cover-dark.6PcmVP3XX2TVaNt503y99IXaQ108zXnk1osLG4-hM4U.png)SwiftUI Fundamentalsby Natalia Panferova$35](https://books.nilcoalescing.com/swiftui-fundamentals?utm_term=SwiftUIDataDependenciesAndTheirEffectOnViewUpdates&utm_source=nilcoalescing&utm_content=swiftui-fundamentals-banner)\n\n##  #  @State and @Binding \n\nThe [`@State`](https://developer.apple.com/documentation/SwiftUI/State%28%29) macro allows a view to store mutable data in storage managed by SwiftUI. The storage is associated with the view's identity, so the value persists as SwiftUI creates new instances of the view during updates.\n\nWhen another view needs to modify the value, we can pass it a binding. Applying `$` to the state property produces a [`Binding`](https://developer.apple.com/documentation/swiftui/binding) to the same storage. Another view can accept this binding through an `@Binding` property, which allows it to read and write the value without storing another copy.\n\nState and bindings affect view updates differently from plain stored properties. Unlike plain stored inputs, which remain part of the view value whether or not the body reads them, state and bindings establish a dependency only when the view accesses their values during body evaluation.\n\nUntil the value is first read in `body`, changing it does not trigger a body evaluation. After the first access, it remains a dependency as long as the view keeps the same identity, even if subsequent body evaluations no longer use it.\n\nThis distinction becomes important when a view can modify a bound value without using it to produce its current interface. In the example below, a conditional branch lets us compare the resulting body evaluations before and after the value is first read.\n    \n    \n    struct BirdDetailsView: View {\n        let birdName: String\n        @Binding var sightingCount: Int\n    \n        @State private var showsSightingCount = false\n    \n        var body: some View {\n            VStack {\n                Image(birdName)\n                Text(\"Bird: \\(birdName)\")\n    \n                Toggle(\n                    \"Show sighting count\",\n                    isOn: $showsSightingCount\n                )\n    \n                if showsSightingCount {\n                    Text(\n                        \"Number of sightings: \\(sightingCount)\"\n                    )\n                }\n    \n                Spacer()\n    \n                Button(\"Add sighting\") {\n                    // Before the count has been shown, changing it does not\n                    // reevaluate body. After it has been accessed once, it does.\n                    sightingCount += 1\n                }\n            }\n        }\n    }\n    \n\nWhen `BirdDetailsView` first evaluates its body, `showsSightingCount` is `false`, so the conditional branch does not access `sightingCount`. Pressing the button changes the state through the binding without running the `BirdDetailsView` body again.\n\nTurning on the toggle changes `showsSightingCount` and runs the body. The conditional branch now accesses `sightingCount` and displays its latest value, including any sightings added while the count was hidden. This access establishes the dependency, so further changes to the bound value run the body and update the displayed count.\n\nIf we turn the toggle off again, the next body evaluation no longer accesses `sightingCount`, but the dependency established by the earlier access remains. Adding another sighting still runs the body.\n\nConditional access can delay when a state or a binding becomes a dependency, but it does not remove that dependency after the value has been read. Changes to the value will continue to trigger the view's body reevaluations. If the resulting body evaluations become expensive, we can move the part of the interface that reads the value into a separate view, limiting updates to that part of the hierarchy.\n\n##  #  @Environment and @FocusedValue \n\nEnvironment values allow a view to read data supplied through its surrounding hierarchy without passing it through every initializer along the way. SwiftUI provides many built-in values, and we can define custom ones by adding an [`@Entry`](https://developer.apple.com/documentation/swiftui/entry%28%29) property to `EnvironmentValues`. A view reads a specific value by declaring an [`@Environment`](https://developer.apple.com/documentation/swiftui/environment) property with its key path.\n\nFocused values provide similar access to data associated with the current focus. A view publishes a value with the `focusedValue()` modifier, and another view reads it by declaring a [`@FocusedValue`](https://developer.apple.com/documentation/swiftui/focusedvalue) property. The value comes from the focused view or its nearest ancestor that provides it, and becomes `nil` when no value is available for that key.\n\nDeclaring an `@Environment` or `@FocusedValue` property inside a view struct subscribes the view to updates in the corresponding value. When a new value is supplied, SwiftUI compares it with the previous one using the same rules discussed for plain stored inputs. If the comparison finds a change, SwiftUI reevaluates the view's body even when the property is not used to produce its interface.\n\nAn unused subscription can remain after the part of the interface that needed the value has been removed or moved elsewhere during a refactor. In the example below, `BirdDetailsView` still declares an environment property for `sightingCount`, but its body no longer displays the count.\n    \n    \n    extension EnvironmentValues {\n        @Entry var sightingCount = 0\n    }\n    \n    struct BirdDetailsView: View {\n        let birdName: String\n    \n        @Environment(\\.sightingCount) private var sightingCount\n    \n        var body: some View {\n            VStack {\n                Image(birdName)\n                Text(\"Bird: \\(birdName)\")\n            }\n        }\n    }\n    \n    struct BirdSightingsView: View {\n        @State private var sightingCount = 0\n    \n        var body: some View {\n            VStack {\n                BirdDetailsView(birdName: \"Fantail\")\n                    .environment(\\.sightingCount, sightingCount)\n    \n                Text(\"Number of sightings: \\(sightingCount)\")\n    \n                Spacer()\n    \n                Button(\"Add sighting\") {\n                    // Changing the environment value runs BirdDetailsView's body,\n                    // even though the view does not use sightingCount.\n                    sightingCount += 1\n                }\n            }\n            .padding()\n        }\n    }\n    \n\nIncrementing `sightingCount` supplies a different environment value to `BirdDetailsView`. Because the view declares the corresponding `@Environment` property, its body runs even though the resulting interface only depends on `birdName`.\n\nThe same behavior applies to `@FocusedValue`. As focus moves, the value for a key can change or become `nil`. A view that declares the corresponding property will have its body reevaluated even if it does not read the focused value.\n\nWe should remove unused `@Environment` and `@FocusedValue` properties when a view no longer needs them. This removes the subscriptions and prevents changes in the environment or focus hierarchy from running a body that does not produce a different interface.\n\n##  #  @Observable models \n\nApplying the [`@Observable`](https://developer.apple.com/documentation/observation/observable%28%29) macro to a class adds observation to its stored properties. A view that creates the model can keep its instance in `@State`, while other views can receive the same instance through a regular stored property or read it from the environment.\n\nSwiftUI tracks the observable properties that a view reads while evaluating its body. Each read establishes a dependency on that property rather than on the model as a whole. Changing a property reevaluates the views that depend on it, while views that only read other properties on the same model remain unchanged.\n\nProperty-level tracking allows different views to depend on different parts of the same model. In the example below, `BirdSightingsView` reads `sightingCount`, while `BirdDetailsView` only reads `name`.\n    \n    \n    @Observable\n    final class BirdModel {\n        var name: String\n        var sightingCount = 0\n    \n        init(name: String) {\n            self.name = name\n        }\n    }\n    \n    struct BirdDetailsView: View {\n        let bird: BirdModel\n    \n        var body: some View {\n            VStack {\n                Image(bird.name)\n                Text(\"Bird: \\(bird.name)\")\n            }\n        }\n    }\n    \n    struct BirdSightingsView: View {\n        @State private var bird = BirdModel(name: \"Fantail\")\n    \n        var body: some View {\n            VStack {\n                BirdDetailsView(bird: bird)\n    \n                Text(\n                    \"Number of sightings: \\(bird.sightingCount)\"\n                )\n    \n                Spacer()\n    \n                Button(\"Add sighting\") {\n                    // BirdDetailsView does not read sightingCount,\n                    // so changing the count does not run its body.\n                    bird.sightingCount += 1\n                }\n    \n                Button(\"Show kea\") {\n                    // BirdDetailsView reads name, so changing the name\n                    // runs its body and updates the displayed bird.\n                    bird.name = \"Kea\"\n                }\n            }\n            .padding()\n        }\n    }\n    \n\nAdding a sighting changes `sightingCount`, which reevaluates `BirdSightingsView` because its body reads that property. `BirdDetailsView` only reads `name`, so its body does not run. Changing `name` reevaluates `BirdDetailsView` and updates the displayed bird without reevaluating `BirdSightingsView`.\n\nObservation recalculates a view's property dependencies during every body evaluation. If an observable property is read inside a conditional branch, it becomes a dependency only when that branch runs. A later body evaluation that skips the branch removes the dependency, so subsequent changes to the property no longer reevaluate the body. This differs from state and bindings, where a dependency remains after the value has been read.\n\n##  #  ObservableObject property wrappers \n\nAn [`ObservableObject`](https://developer.apple.com/documentation/combine/observableobject) publishes changes through its `objectWillChange` publisher. Marking a property with [`@Published`](https://developer.apple.com/documentation/combine/published) makes setting that property send the notification automatically.\n\nA view subscribes to these notifications by declaring the object with one of SwiftUI's object-specific property wrappers. [`@StateObject`](https://developer.apple.com/documentation/swiftui/stateobject) creates and retains an instance for the lifetime of the view's identity, [`@ObservedObject`](https://developer.apple.com/documentation/swiftui/observedobject) observes an instance supplied to the view, and [`@EnvironmentObject`](https://developer.apple.com/documentation/swiftui/environmentobject) retrieves an instance supplied through the surrounding hierarchy.\n\nThese wrappers differ in how the view obtains the object, but they all subscribe to the same object-wide change notification. When `objectWillChange` emits, SwiftUI reevaluates a subscribed view without considering which published properties its body reads. A change to any `@Published` property will run the body even when the changed property is not used to produce the interface.\n\nWe can see this subscription behavior when a view owns an object and passes it to a subview, even though its own body does not read any of the object's published properties. In the example below, `BirdSightingsView` stores a `BirdModel` in `@StateObject` and supplies it to `BirdDetailsView`. The parent only mutates `sightingCount` from the button action.\n    \n    \n    final class BirdModel: ObservableObject {\n        @Published var name: String\n        @Published var sightingCount = 0\n    \n        init(name: String) {\n            self.name = name\n        }\n    }\n    \n    struct BirdDetailsView: View {\n        @ObservedObject var bird: BirdModel\n    \n        var body: some View {\n            VStack {\n                Image(bird.name)\n                Text(\"Bird: \\(bird.name)\")\n            }\n        }\n    }\n    \n    struct BirdSightingsView: View {\n        @StateObject private var bird = BirdModel(name: \"Fantail\")\n    \n        var body: some View {\n            VStack {\n                BirdDetailsView(bird: bird)\n    \n                Spacer()\n    \n                Button(\"Add sighting\") {\n                    // Changing sightingCount runs BirdSightingsView's body,\n                    // even though the body does not read the property.\n                    bird.sightingCount += 1\n                }\n            }\n            .padding()\n        }\n    }\n    \n\nPressing the button emits `objectWillChange`. `BirdSightingsView` reevaluates its body because it declares the model with `@StateObject`, even though it does not read `name` or `sightingCount`. `BirdDetailsView` also reevaluates because its `@ObservedObject` property subscribes to the same notification, even though its body only reads `name`.\n\nWith an `@Observable` model, passing the model reference to `BirdDetailsView` would not make `BirdSightingsView` depend on its properties. Changing `sightingCount` in the same example would reevaluate neither view because neither body reads that property.\n\nThis object-wide invalidation is why, in modern SwiftUI projects, we should prefer the newer Observation APIs over `ObservableObject` and its property wrappers.\n\n  \nIf you are looking to build a strong foundation in SwiftUI, my book [SwiftUI Fundamentals](https://books.nilcoalescing.com/swiftui-fundamentals?utm_term=SwiftUIDataDependenciesAndTheirEffectOnViewUpdates&utm_content=inline&utm_medium=blog&utm_source=nilcoalescing) takes a deep dive into the framework's core principles and APIs to help you understand how it works under the hood and how to use it effectively in your projects. And my more recent book [The SwiftUI Way](https://books.nilcoalescing.com/the-swiftui-way?utm_term=SwiftUIDataDependenciesAndTheirEffectOnViewUpdates&utm_content=inline&utm_source=nilcoalescing&utm_medium=blog) helps you adopt recommended patterns, avoid common pitfalls, and use SwiftUI's native tools appropriately to work with the framework rather than against it.\n\nFor more resources on Swift and SwiftUI, check out my other [books](https://books.nilcoalescing.com?utm_term=SwiftUIDataDependenciesAndTheirEffectOnViewUpdates&utm_content=inline&utm_medium=blog&utm_source=nilcoalescing) and book [bundles](https://books.nilcoalescing.com/bundles?utm_term=SwiftUIDataDependenciesAndTheirEffectOnViewUpdates&utm_content=inline&utm_medium=blog&utm_source=nilcoalescing).\n\n[![The SwiftUI Way by Natalia Panferova book cover](/static/BookBanner/the-swiftui-way/Book-cover.LSX6kb9siFvPsdJJoKjE7k2V-YyzIkgZiap1hqFm7zI.png)![The SwiftUI Way by Natalia Panferova book cover](/static/BookBanner/the-swiftui-way/Book-cover-dark.vb6G8Vn24WJgQSxjB7I-2IGCOdWRLAozdrx4MQLly7w.png)Work with SwiftUI. Not against it.$35A field guide to SwiftUI patterns and anti-patternsThe SwiftUI Wayby Natalia Panferova\n\n  * Avoid common SwiftUI pitfalls\n  * Build deeper intuition for the framework\n  * Gain insights from a former SwiftUI Engineer at Apple\n\nWork with SwiftUI. Not against it.A field guide to SwiftUI patterns and anti-patterns![The SwiftUI Way by Natalia Panferova book cover](/static/BookBanner/the-swiftui-way/Book-cover.LSX6kb9siFvPsdJJoKjE7k2V-YyzIkgZiap1hqFm7zI.png)![The SwiftUI Way by Natalia Panferova book cover](/static/BookBanner/the-swiftui-way/Book-cover-dark.vb6G8Vn24WJgQSxjB7I-2IGCOdWRLAozdrx4MQLly7w.png)The SwiftUI Wayby Natalia Panferova$35](https://books.nilcoalescing.com/the-swiftui-way?utm_source=nilcoalescing&utm_content=the-swiftui-way-banner&utm_term=SwiftUIDataDependenciesAndTheirEffectOnViewUpdates)","body_html":"<h1 id=\"swiftui-data-dependencies-and-their-effect-on-view-updates\">SwiftUI data dependencies and their effect on view updates</h1>\n<p>A SwiftUI view can receive the data it needs in many forms. We can pass values through stored properties, keep local state, accept bindings, read values from the environment or use an observable model. These declarations all connect data to a view, but they do not all become dependencies in the same way or have the same effect on view updates.</p>\n<p>In this post, we will compare these different types of data dependencies and see why some changes cause a view&#39;s body to run again while others do not. We will also look at cases where a value appears unchanged from the app&#39;s perspective but still leads to another body evaluation.</p>\n<h2 id=\"plain-stored-properties\">#  Plain stored properties</h2>\n<p>Regular stored properties are the most direct form of data dependency in a SwiftUI view. Values passed when the view is initialized become part of the view value. When a parent produces a new instance of the view, SwiftUI compares its stored inputs with the previous instance to determine whether the body needs to be evaluated again.</p>\n<h3 id=\"value-types\">#  Value types</h3>\n<p>For value-type inputs, SwiftUI compares the values stored in one instance of the view with those in the next. This comparison extends through a value&#39;s stored properties, so it applies both to types such as <code>String</code> and <code>Int</code> and to custom structures composed of multiple values.</p>\n<p>In the example below, both <code>birdName</code> and <code>sightingCount</code> are value-type inputs. If either value changes, SwiftUI reevaluates the <code>BirdDetailsView</code> body.</p>\n<pre><code>struct BirdDetailsView: View {\n    let birdName: String\n    let sightingCount: Int\n\n    var body: some View {\n        VStack {\n            Image(birdName)\n            Text(&quot;Bird: \\(birdName)&quot;)\n            Text(&quot;Number of sightings: \\(sightingCount)&quot;)\n        }\n    }\n}</code></pre>\n<p>Plain stored properties remain part of a view&#39;s inputs regardless of whether <code>body</code> accesses them. SwiftUI considers all stored values when comparing successive instances of a view. As a result, changing an unused input can still cause the view&#39;s <code>body</code> to be reevaluated.</p>\n<p>For example, we could move the text that displays the sighting count from <code>BirdDetailsView</code> into its parent but forget to remove <code>sightingCount</code> from the child view&#39;s inputs.</p>\n<pre><code>struct BirdDetailsView: View {\n    let birdName: String\n\n    // This property is not used in body, but SwiftUI still compares it\n    // when the parent creates a new BirdDetailsView value.\n    let sightingCount: Int\n\n    var body: some View {\n        VStack {\n            Image(birdName)\n            Text(&quot;Bird: \\(birdName)&quot;)\n        }\n    }\n}\n\nstruct BirdSightingsView: View {\n    @State private var sightingCount = 0\n\n    private let birdName = &quot;Fantail&quot;\n\n    var body: some View {\n        VStack {\n            BirdDetailsView(\n                birdName: birdName,\n                sightingCount: sightingCount\n            )\n            \n            Text(&quot;Number of sightings: \\(sightingCount)&quot;)\n            \n            Spacer()\n\n            Button(&quot;Add sighting&quot;) {\n                // The state update produces a new BirdDetailsView value with\n                // a different sightingCount input, so its body runs again.\n                sightingCount += 1\n            }\n        }\n        .padding()\n    }\n}</code></pre>\n<p>Incrementing the count will create a <code>BirdDetailsView</code> with a different stored value, so its <code>body</code> will run again even though it no longer reads <code>sightingCount</code>.</p>\n<p>When passing value types into a view, we should only include the values it needs to produce its content. Otherwise, a change to an unused input can make the view&#39;s body run again without affecting the resulting interface.</p>\n<h3 id=\"reference-types\">#  Reference types</h3>\n<p>Unlike value types, reference-type inputs are not compared by examining the values stored on the instance. SwiftUI checks whether the view received the same class instance as before. A regular stored input only appears different when the parent passes another instance.</p>\n<p>For example, we can store the bird data in a plain class and replace the instance when selecting a different bird.</p>\n<pre><code>final class BirdRecord {\n    var name: String\n    var sightingCount: Int\n\n    init(name: String, sightingCount: Int) {\n        self.name = name\n        self.sightingCount = sightingCount\n    }\n}\n\nstruct BirdDetailsView: View {\n    let bird: BirdRecord\n\n    var body: some View {\n        VStack {\n            Image(bird.name)\n            Text(&quot;Bird: \\(bird.name)&quot;)\n            Text(&quot;Number of sightings: \\(bird.sightingCount)&quot;)\n        }\n    }\n}\n\nstruct BirdSightingsView: View {\n    @State private var bird = BirdRecord(\n        name: &quot;Fantail&quot;,\n        sightingCount: 4\n    )\n\n    var body: some View {\n        VStack {\n            BirdDetailsView(bird: bird)\n\n            Spacer()\n\n            Button(&quot;Show kea&quot;) {\n                bird = BirdRecord(\n                    name: &quot;Kea&quot;,\n                    sightingCount: 2\n                )\n            }\n        }\n        .padding()\n    }\n}</code></pre>\n<p>Pressing the button replaces the stored reference with a new <code>BirdRecord</code> instance. <code>BirdDetailsView</code> receives a different instance, so SwiftUI reevaluates its body.</p>\n<p>Changes made to properties on the existing instance behave differently. Mutating a property does not change the reference stored by the view, so the input still appears unchanged to SwiftUI.</p>\n<p>In the example below, pressing the button updates the properties on the existing <code>BirdRecord</code> instead of replacing it with another instance.</p>\n<pre><code>struct BirdSightingsView: View {\n    @State private var bird = BirdRecord(\n        name: &quot;Fantail&quot;,\n        sightingCount: 4\n    )\n\n    var body: some View {\n        VStack {\n            BirdDetailsView(bird: bird)\n\n            Spacer()\n\n            Button(&quot;Show kea&quot;) {\n                // Mutating these properties keeps the same BirdRecord instance,\n                // so BirdDetailsView&#39;s body does not run.\n                bird.name = &quot;Kea&quot;\n                bird.sightingCount = 2\n            }\n        }\n        .padding()\n    }\n}</code></pre>\n<p><code>BirdDetailsView</code> continues to display the fantail and its original sighting count because its body does not run after the properties are mutated.</p>\n<p>Plain reference types work as view inputs when replacing the entire instance represents a change in the data. For models that change in place, we need an observation mechanism to keep the interface in sync.</p>\n<h3 id=\"closures\">#  Closures</h3>\n<p>Closures can also be passed into a view through regular stored properties. Like the value and reference types we discussed, they become part of the view value SwiftUI examines when the parent produces a new instance. The difference is that SwiftUI cannot reliably determine whether two closure values are the same.</p>\n<p>We can see the effect of this comparison behavior when a state change reevaluates a parent&#39;s body without changing any data used by one of its subviews. In the example below, <code>BirdSightingsView</code> owns a watchlist and passes <code>BirdDetailsView</code> a closure for adding the displayed bird to it. It also stores <code>sightingCount</code>, which is not passed to <code>BirdDetailsView</code>. When <code>sightingCount</code> changes, <code>BirdSightingsView</code> reevaluates its body and creates the <code>addToWatchlist</code> closure again, so SwiftUI treats <code>BirdDetailsView</code> as changed and runs its body even though none of the values used to produce its interface have changed.</p>\n<pre><code>struct BirdDetailsView: View {\n    let birdName: String\n    let addToWatchlist: () -&gt; Void\n\n    var body: some View {\n        VStack {\n            Image(birdName)\n            Text(&quot;Bird: \\(birdName)&quot;)\n\n            Button(\n                &quot;Add to watchlist&quot;,\n                action: addToWatchlist\n            )\n        }\n    }\n}\n\nstruct BirdSightingsView: View {\n    @State private var sightingCount = 0\n    @State private var watchlist: Set&lt;String&gt; = []\n\n    private let birdName = &quot;Fantail&quot;\n\n    var body: some View {\n        VStack {\n            BirdDetailsView(\n                birdName: birdName,\n                addToWatchlist: {\n                    watchlist.formUnion([birdName])\n                }\n            )\n\n            Text(&quot;Number of sightings: \\(sightingCount)&quot;)\n            Text(&quot;Birds in your watchlist: \\(watchlist.count)&quot;)\n\n            Spacer()\n\n            Button(&quot;Add sighting&quot;) {\n                // Updating sightingCount makes BirdDetailsView&#39;s body run again,\n                // even though it does not change any values used to produce its interface.\n                sightingCount += 1\n            }\n        }\n        .padding()\n    }\n}</code></pre>\n<p><code>BirdDetailsView</code> produces the same interface after this additional body evaluation because <code>birdName</code> has not changed. The cost may be negligible for a view this small, but it can become significant when the parent reevaluates frequently or when the subview performs more work in its body.</p>\n<p>We should avoid passing closures to subviews when possible, and prefer inputs that SwiftUI can compare reliably.</p>\n<p>[SwiftUI Fundamentals by Natalia Panferova book coverSwiftUI Fundamentals by Natalia Panferova book coverDeepen your understanding of SwiftUI!$35The essential guide to SwiftUI core concepts and APIsSwiftUI Fundamentalsby Natalia Panferova</p>\n<ul><li>Explore the key APIs and design patterns that form the foundation of SwiftUI</li><li>Develop a deep, practical understanding of how SwiftUI works under the hood</li><li>Learn from a former Apple engineer who worked on widely used SwiftUI APIs</li></ul>\n<p>Deepen your understanding of SwiftUI!The essential guide to SwiftUI core concepts and APIsSwiftUI Fundamentals by Natalia Panferova book coverSwiftUI Fundamentals by Natalia Panferova book coverSwiftUI Fundamentalsby Natalia Panferova$35](<a href=\"https://books.nilcoalescing.com/swiftui-fundamentals?utm_term=SwiftUIDataDependenciesAndTheirEffectOnViewUpdates&amp;utm_source=nilcoalescing&amp;utm_content=swiftui-fundamentals-banner\" rel=\"nofollow ugc noopener\">https://books.nilcoalescing.com/swiftui-fundamentals?utm_term=SwiftUIDataDependenciesAndTheirEffectOnViewUpdates&amp;utm_source=nilcoalescing&amp;utm_content=swiftui-fundamentals-banner</a>)</p>\n<h2 id=\"state-and-binding\">#  @State and @Binding</h2>\n<p>The <a href=\"https://developer.apple.com/documentation/SwiftUI/State%28%29\" rel=\"nofollow ugc noopener\"><code>@State</code></a> macro allows a view to store mutable data in storage managed by SwiftUI. The storage is associated with the view&#39;s identity, so the value persists as SwiftUI creates new instances of the view during updates.</p>\n<p>When another view needs to modify the value, we can pass it a binding. Applying <code>$</code> to the state property produces a <a href=\"https://developer.apple.com/documentation/swiftui/binding\" rel=\"nofollow ugc noopener\"><code>Binding</code></a> to the same storage. Another view can accept this binding through an <code>@Binding</code> property, which allows it to read and write the value without storing another copy.</p>\n<p>State and bindings affect view updates differently from plain stored properties. Unlike plain stored inputs, which remain part of the view value whether or not the body reads them, state and bindings establish a dependency only when the view accesses their values during body evaluation.</p>\n<p>Until the value is first read in <code>body</code>, changing it does not trigger a body evaluation. After the first access, it remains a dependency as long as the view keeps the same identity, even if subsequent body evaluations no longer use it.</p>\n<p>This distinction becomes important when a view can modify a bound value without using it to produce its current interface. In the example below, a conditional branch lets us compare the resulting body evaluations before and after the value is first read.</p>\n<pre><code>struct BirdDetailsView: View {\n    let birdName: String\n    @Binding var sightingCount: Int\n\n    @State private var showsSightingCount = false\n\n    var body: some View {\n        VStack {\n            Image(birdName)\n            Text(&quot;Bird: \\(birdName)&quot;)\n\n            Toggle(\n                &quot;Show sighting count&quot;,\n                isOn: $showsSightingCount\n            )\n\n            if showsSightingCount {\n                Text(\n                    &quot;Number of sightings: \\(sightingCount)&quot;\n                )\n            }\n\n            Spacer()\n\n            Button(&quot;Add sighting&quot;) {\n                // Before the count has been shown, changing it does not\n                // reevaluate body. After it has been accessed once, it does.\n                sightingCount += 1\n            }\n        }\n    }\n}</code></pre>\n<p>When <code>BirdDetailsView</code> first evaluates its body, <code>showsSightingCount</code> is <code>false</code>, so the conditional branch does not access <code>sightingCount</code>. Pressing the button changes the state through the binding without running the <code>BirdDetailsView</code> body again.</p>\n<p>Turning on the toggle changes <code>showsSightingCount</code> and runs the body. The conditional branch now accesses <code>sightingCount</code> and displays its latest value, including any sightings added while the count was hidden. This access establishes the dependency, so further changes to the bound value run the body and update the displayed count.</p>\n<p>If we turn the toggle off again, the next body evaluation no longer accesses <code>sightingCount</code>, but the dependency established by the earlier access remains. Adding another sighting still runs the body.</p>\n<p>Conditional access can delay when a state or a binding becomes a dependency, but it does not remove that dependency after the value has been read. Changes to the value will continue to trigger the view&#39;s body reevaluations. If the resulting body evaluations become expensive, we can move the part of the interface that reads the value into a separate view, limiting updates to that part of the hierarchy.</p>\n<h2 id=\"environment-and-focusedvalue\">#  @Environment and @FocusedValue</h2>\n<p>Environment values allow a view to read data supplied through its surrounding hierarchy without passing it through every initializer along the way. SwiftUI provides many built-in values, and we can define custom ones by adding an <a href=\"https://developer.apple.com/documentation/swiftui/entry%28%29\" rel=\"nofollow ugc noopener\"><code>@Entry</code></a> property to <code>EnvironmentValues</code>. A view reads a specific value by declaring an <a href=\"https://developer.apple.com/documentation/swiftui/environment\" rel=\"nofollow ugc noopener\"><code>@Environment</code></a> property with its key path.</p>\n<p>Focused values provide similar access to data associated with the current focus. A view publishes a value with the <code>focusedValue()</code> modifier, and another view reads it by declaring a <a href=\"https://developer.apple.com/documentation/swiftui/focusedvalue\" rel=\"nofollow ugc noopener\"><code>@FocusedValue</code></a> property. The value comes from the focused view or its nearest ancestor that provides it, and becomes <code>nil</code> when no value is available for that key.</p>\n<p>Declaring an <code>@Environment</code> or <code>@FocusedValue</code> property inside a view struct subscribes the view to updates in the corresponding value. When a new value is supplied, SwiftUI compares it with the previous one using the same rules discussed for plain stored inputs. If the comparison finds a change, SwiftUI reevaluates the view&#39;s body even when the property is not used to produce its interface.</p>\n<p>An unused subscription can remain after the part of the interface that needed the value has been removed or moved elsewhere during a refactor. In the example below, <code>BirdDetailsView</code> still declares an environment property for <code>sightingCount</code>, but its body no longer displays the count.</p>\n<pre><code>extension EnvironmentValues {\n    @Entry var sightingCount = 0\n}\n\nstruct BirdDetailsView: View {\n    let birdName: String\n\n    @Environment(\\.sightingCount) private var sightingCount\n\n    var body: some View {\n        VStack {\n            Image(birdName)\n            Text(&quot;Bird: \\(birdName)&quot;)\n        }\n    }\n}\n\nstruct BirdSightingsView: View {\n    @State private var sightingCount = 0\n\n    var body: some View {\n        VStack {\n            BirdDetailsView(birdName: &quot;Fantail&quot;)\n                .environment(\\.sightingCount, sightingCount)\n\n            Text(&quot;Number of sightings: \\(sightingCount)&quot;)\n\n            Spacer()\n\n            Button(&quot;Add sighting&quot;) {\n                // Changing the environment value runs BirdDetailsView&#39;s body,\n                // even though the view does not use sightingCount.\n                sightingCount += 1\n            }\n        }\n        .padding()\n    }\n}</code></pre>\n<p>Incrementing <code>sightingCount</code> supplies a different environment value to <code>BirdDetailsView</code>. Because the view declares the corresponding <code>@Environment</code> property, its body runs even though the resulting interface only depends on <code>birdName</code>.</p>\n<p>The same behavior applies to <code>@FocusedValue</code>. As focus moves, the value for a key can change or become <code>nil</code>. A view that declares the corresponding property will have its body reevaluated even if it does not read the focused value.</p>\n<p>We should remove unused <code>@Environment</code> and <code>@FocusedValue</code> properties when a view no longer needs them. This removes the subscriptions and prevents changes in the environment or focus hierarchy from running a body that does not produce a different interface.</p>\n<h2 id=\"observable-models\">#  @Observable models</h2>\n<p>Applying the <a href=\"https://developer.apple.com/documentation/observation/observable%28%29\" rel=\"nofollow ugc noopener\"><code>@Observable</code></a> macro to a class adds observation to its stored properties. A view that creates the model can keep its instance in <code>@State</code>, while other views can receive the same instance through a regular stored property or read it from the environment.</p>\n<p>SwiftUI tracks the observable properties that a view reads while evaluating its body. Each read establishes a dependency on that property rather than on the model as a whole. Changing a property reevaluates the views that depend on it, while views that only read other properties on the same model remain unchanged.</p>\n<p>Property-level tracking allows different views to depend on different parts of the same model. In the example below, <code>BirdSightingsView</code> reads <code>sightingCount</code>, while <code>BirdDetailsView</code> only reads <code>name</code>.</p>\n<pre><code>@Observable\nfinal class BirdModel {\n    var name: String\n    var sightingCount = 0\n\n    init(name: String) {\n        self.name = name\n    }\n}\n\nstruct BirdDetailsView: View {\n    let bird: BirdModel\n\n    var body: some View {\n        VStack {\n            Image(bird.name)\n            Text(&quot;Bird: \\(bird.name)&quot;)\n        }\n    }\n}\n\nstruct BirdSightingsView: View {\n    @State private var bird = BirdModel(name: &quot;Fantail&quot;)\n\n    var body: some View {\n        VStack {\n            BirdDetailsView(bird: bird)\n\n            Text(\n                &quot;Number of sightings: \\(bird.sightingCount)&quot;\n            )\n\n            Spacer()\n\n            Button(&quot;Add sighting&quot;) {\n                // BirdDetailsView does not read sightingCount,\n                // so changing the count does not run its body.\n                bird.sightingCount += 1\n            }\n\n            Button(&quot;Show kea&quot;) {\n                // BirdDetailsView reads name, so changing the name\n                // runs its body and updates the displayed bird.\n                bird.name = &quot;Kea&quot;\n            }\n        }\n        .padding()\n    }\n}</code></pre>\n<p>Adding a sighting changes <code>sightingCount</code>, which reevaluates <code>BirdSightingsView</code> because its body reads that property. <code>BirdDetailsView</code> only reads <code>name</code>, so its body does not run. Changing <code>name</code> reevaluates <code>BirdDetailsView</code> and updates the displayed bird without reevaluating <code>BirdSightingsView</code>.</p>\n<p>Observation recalculates a view&#39;s property dependencies during every body evaluation. If an observable property is read inside a conditional branch, it becomes a dependency only when that branch runs. A later body evaluation that skips the branch removes the dependency, so subsequent changes to the property no longer reevaluate the body. This differs from state and bindings, where a dependency remains after the value has been read.</p>\n<h2 id=\"observableobject-property-wrappers\">#  ObservableObject property wrappers</h2>\n<p>An <a href=\"https://developer.apple.com/documentation/combine/observableobject\" rel=\"nofollow ugc noopener\"><code>ObservableObject</code></a> publishes changes through its <code>objectWillChange</code> publisher. Marking a property with <a href=\"https://developer.apple.com/documentation/combine/published\" rel=\"nofollow ugc noopener\"><code>@Published</code></a> makes setting that property send the notification automatically.</p>\n<p>A view subscribes to these notifications by declaring the object with one of SwiftUI&#39;s object-specific property wrappers. <a href=\"https://developer.apple.com/documentation/swiftui/stateobject\" rel=\"nofollow ugc noopener\"><code>@StateObject</code></a> creates and retains an instance for the lifetime of the view&#39;s identity, <a href=\"https://developer.apple.com/documentation/swiftui/observedobject\" rel=\"nofollow ugc noopener\"><code>@ObservedObject</code></a> observes an instance supplied to the view, and <a href=\"https://developer.apple.com/documentation/swiftui/environmentobject\" rel=\"nofollow ugc noopener\"><code>@EnvironmentObject</code></a> retrieves an instance supplied through the surrounding hierarchy.</p>\n<p>These wrappers differ in how the view obtains the object, but they all subscribe to the same object-wide change notification. When <code>objectWillChange</code> emits, SwiftUI reevaluates a subscribed view without considering which published properties its body reads. A change to any <code>@Published</code> property will run the body even when the changed property is not used to produce the interface.</p>\n<p>We can see this subscription behavior when a view owns an object and passes it to a subview, even though its own body does not read any of the object&#39;s published properties. In the example below, <code>BirdSightingsView</code> stores a <code>BirdModel</code> in <code>@StateObject</code> and supplies it to <code>BirdDetailsView</code>. The parent only mutates <code>sightingCount</code> from the button action.</p>\n<pre><code>final class BirdModel: ObservableObject {\n    @Published var name: String\n    @Published var sightingCount = 0\n\n    init(name: String) {\n        self.name = name\n    }\n}\n\nstruct BirdDetailsView: View {\n    @ObservedObject var bird: BirdModel\n\n    var body: some View {\n        VStack {\n            Image(bird.name)\n            Text(&quot;Bird: \\(bird.name)&quot;)\n        }\n    }\n}\n\nstruct BirdSightingsView: View {\n    @StateObject private var bird = BirdModel(name: &quot;Fantail&quot;)\n\n    var body: some View {\n        VStack {\n            BirdDetailsView(bird: bird)\n\n            Spacer()\n\n            Button(&quot;Add sighting&quot;) {\n                // Changing sightingCount runs BirdSightingsView&#39;s body,\n                // even though the body does not read the property.\n                bird.sightingCount += 1\n            }\n        }\n        .padding()\n    }\n}</code></pre>\n<p>Pressing the button emits <code>objectWillChange</code>. <code>BirdSightingsView</code> reevaluates its body because it declares the model with <code>@StateObject</code>, even though it does not read <code>name</code> or <code>sightingCount</code>. <code>BirdDetailsView</code> also reevaluates because its <code>@ObservedObject</code> property subscribes to the same notification, even though its body only reads <code>name</code>.</p>\n<p>With an <code>@Observable</code> model, passing the model reference to <code>BirdDetailsView</code> would not make <code>BirdSightingsView</code> depend on its properties. Changing <code>sightingCount</code> in the same example would reevaluate neither view because neither body reads that property.</p>\n<p>This object-wide invalidation is why, in modern SwiftUI projects, we should prefer the newer Observation APIs over <code>ObservableObject</code> and its property wrappers.</p>\n<p>If you are looking to build a strong foundation in SwiftUI, my book <a href=\"https://books.nilcoalescing.com/swiftui-fundamentals?utm_term=SwiftUIDataDependenciesAndTheirEffectOnViewUpdates&amp;utm_content=inline&amp;utm_medium=blog&amp;utm_source=nilcoalescing\" rel=\"nofollow ugc noopener\">SwiftUI Fundamentals</a> takes a deep dive into the framework&#39;s core principles and APIs to help you understand how it works under the hood and how to use it effectively in your projects. And my more recent book <a href=\"https://books.nilcoalescing.com/the-swiftui-way?utm_term=SwiftUIDataDependenciesAndTheirEffectOnViewUpdates&amp;utm_content=inline&amp;utm_source=nilcoalescing&amp;utm_medium=blog\" rel=\"nofollow ugc noopener\">The SwiftUI Way</a> helps you adopt recommended patterns, avoid common pitfalls, and use SwiftUI&#39;s native tools appropriately to work with the framework rather than against it.</p>\n<p>For more resources on Swift and SwiftUI, check out my other <a href=\"https://books.nilcoalescing.com?utm_term=SwiftUIDataDependenciesAndTheirEffectOnViewUpdates&amp;utm_content=inline&amp;utm_medium=blog&amp;utm_source=nilcoalescing\" rel=\"nofollow ugc noopener\">books</a> and book <a href=\"https://books.nilcoalescing.com/bundles?utm_term=SwiftUIDataDependenciesAndTheirEffectOnViewUpdates&amp;utm_content=inline&amp;utm_medium=blog&amp;utm_source=nilcoalescing\" rel=\"nofollow ugc noopener\">bundles</a>.</p>\n<p>[The SwiftUI Way by Natalia Panferova book coverThe SwiftUI Way by Natalia Panferova book coverWork with SwiftUI. Not against it.$35A field guide to SwiftUI patterns and anti-patternsThe SwiftUI Wayby Natalia Panferova</p>\n<ul><li>Avoid common SwiftUI pitfalls</li><li>Build deeper intuition for the framework</li><li>Gain insights from a former SwiftUI Engineer at Apple</li></ul>\n<p>Work with SwiftUI. Not against it.A field guide to SwiftUI patterns and anti-patternsThe SwiftUI Way by Natalia Panferova book coverThe SwiftUI Way by Natalia Panferova book coverThe SwiftUI Wayby Natalia Panferova$35](<a href=\"https://books.nilcoalescing.com/the-swiftui-way?utm_source=nilcoalescing&amp;utm_content=the-swiftui-way-banner&amp;utm_term=SwiftUIDataDependenciesAndTheirEffectOnViewUpdates\" rel=\"nofollow ugc noopener\">https://books.nilcoalescing.com/the-swiftui-way?utm_source=nilcoalescing&amp;utm_content=the-swiftui-way-banner&amp;utm_term=SwiftUIDataDependenciesAndTheirEffectOnViewUpdates</a>)</p>","headings":[{"level":1,"text":"SwiftUI data dependencies and their effect on view updates","id":"swiftui-data-dependencies-and-their-effect-on-view-updates"},{"level":2,"text":"#  Plain stored properties","id":"plain-stored-properties"},{"level":3,"text":"#  Value types","id":"value-types"},{"level":3,"text":"#  Reference types","id":"reference-types"},{"level":3,"text":"#  Closures","id":"closures"},{"level":2,"text":"#  @State and @Binding","id":"state-and-binding"},{"level":2,"text":"#  @Environment and @FocusedValue","id":"environment-and-focusedvalue"},{"level":2,"text":"#  @Observable models","id":"observable-models"},{"level":2,"text":"#  ObservableObject property wrappers","id":"observableobject-property-wrappers"}]}}