SwiftUI Transactions & Update Propagation

SwiftUI Transactions & Update Propagation

Source: Dev.to

๐Ÿง  What Is a SwiftUI Transaction? ## ๐Ÿ”„ The Update Propagation Pipeline ## ๐ŸŽฌ Implicit Animations = Transactions ## โš ๏ธ Why Some Views Animate and Others Donโ€™t ## ๐Ÿงฉ The Transaction Type (Explicit Control) ## ๐Ÿšซ Disabling Animations Selectively ## ๐Ÿ” Nested Transactions (Who Wins?) ## โš–๏ธ Transaction vs .animation(_:value:) ## ๐Ÿง  Update Propagation Rules (Critical) ## ๐Ÿงต Async Updates & Transactions ## โš ๏ธ Common Bugs Caused by Transaction Misuse ## ๐Ÿง  Mental Model to Remember ## ๐Ÿš€ Final Thoughts SwiftUI updates donโ€™t just โ€œhappenโ€. Every state change flows through a transaction โ€” and understanding that flow is what separates: Most SwiftUI developers use transactions without realizing it. This post explains what transactions are, how updates propagate, and how SwiftUI decides what animates, what re-renders, and what doesnโ€™t โ€” using modern SwiftUI patterns. A Transaction is a container that carries metadata about a state change. Every time state changes, SwiftUI creates a transaction. Even when you donโ€™t write one. State mutation โ†“ Transaction created โ†“ View invalidation โ†“ Body recomputation โ†“ Layout pass โ†“ Diffing โ†“ Rendering (with or without animation) Transactions flow down the view tree. Children inherit transaction context from parents. No animation code inside views โ€” just data flow. If isVisible changes outside an animation transaction โ†’ no animation. You can intercept or modify transactions: Sometimes you donโ€™t want animations. This prevents animation noise. Text("B") does not animate โ€” even though its parent does. Creates a scoped implicit transaction. SwiftUI propagates updates: This is why clean state ownership matters. Async updates do not automatically animate. To animate async results: Transactions must exist at the moment of mutation. โŒ Animations firing multiple times Cause: repeated state mutations in a loop โŒ Animations not firing Cause: mutation outside transaction โŒ Animations restarting Cause: identity reset (not a transaction issue) โŒ Janky list animations Cause: large diff + implicit animation State changes create transactions. Transactions define animation behavior. Views simply respond. SwiftUI animations are not imperative. They are data-driven side effects of transactions. Understanding transactions gives you: Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: withAnimation(.easeInOut) { isExpanded.toggle() } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: withAnimation(.easeInOut) { isExpanded.toggle() } CODE_BLOCK: withAnimation(.easeInOut) { isExpanded.toggle() } CODE_BLOCK: Text("Hello") .opacity(isVisible ? 1 : 0) Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: Text("Hello") .opacity(isVisible ? 1 : 0) CODE_BLOCK: Text("Hello") .opacity(isVisible ? 1 : 0) CODE_BLOCK: .transaction { tx in tx.animation = .spring() } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: .transaction { tx in tx.animation = .spring() } CODE_BLOCK: .transaction { tx in tx.animation = .spring() } CODE_BLOCK: .transaction { tx in tx.disablesAnimations = true } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: .transaction { tx in tx.disablesAnimations = true } CODE_BLOCK: .transaction { tx in tx.disablesAnimations = true } CODE_BLOCK: withAnimation(.easeIn) { VStack { Text("A") Text("B") .transaction { $0.animation = nil } } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: withAnimation(.easeIn) { VStack { Text("A") Text("B") .transaction { $0.animation = nil } } } CODE_BLOCK: withAnimation(.easeIn) { VStack { Text("A") Text("B") .transaction { $0.animation = nil } } } CODE_BLOCK: .animation(.easeInOut, value: isExpanded) Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: .animation(.easeInOut, value: isExpanded) CODE_BLOCK: .animation(.easeInOut, value: isExpanded) CODE_BLOCK: Task { await load() isLoaded = true // no animation } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: Task { await load() isLoaded = true // no animation } CODE_BLOCK: Task { await load() isLoaded = true // no animation } CODE_BLOCK: await MainActor.run { withAnimation { isLoaded = true } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: await MainActor.run { withAnimation { isLoaded = true } } CODE_BLOCK: await MainActor.run { withAnimation { isLoaded = true } } - smooth, predictable animations - from glitchy, half-animated UI - from views updating โ€œtoo muchโ€ - from state changes not animating at all - whether animations are enabled - what animation to use - whether updates should be immediate - context for view updates - Mutates state - Wraps that mutation in a transaction with animation metadata - propagates that transaction - animates any animatable values affected by the change - changed inside the same transaction - are animatable - and are diffed as changed - override parent animations - disable animations for subtrees - enforce consistent motion - initial layout - list updates - pagination inserts - state restoration - performance-critical paths - inner transactions override outer ones - closest transaction wins - no transaction = inherits parent - .animation(value:) is declarative - withAnimation is imperative - .animation(value:) for simple view-driven animation - withAnimation for event-driven state changes - top-down through the hierarchy - only to views that depend on changed state - stopping at unchanged identity boundaries - sibling views donโ€™t update unless needed - children update only if inputs changed - transactions donโ€™t magically animate everything - disable animations for list updates - animate only user-driven changes - full control over animation behavior - predictable update propagation - smoother UI - better performance - fewer โ€œwhy did this animate?โ€ bugs