What is structural versus explicit identity in SwiftUI, and what happens to state when identity changes?

8.1.2 · L3 · Interview

Predict first

Which body type comes from an if/else and which from a ternary?

import SwiftUI

struct Cond: View { var on: Bool
    var body: some View { if on { Text("A") } else { Text("B") } } }
struct Tern: View { var on: Bool
    var body: some View { Text(on ? "A" : "B") } }
print(type(of: Cond(on: true).body))
print(type(of: Tern(on: true).body))
  • Text, then Text
  • _ConditionalContent<Text, Text>, then Text
  • Text, then _ConditionalContent<Text, Text>

Mental model. Structural identity is “the person in seat 12B”. Explicit identity is a name tag. If you swap the person in the seat, the system thinks it is a new passenger; if the name tag stays, it knows it is the same person who moved.

SwiftUI needs to know, between two evaluations of body, whether a view is the same view (keep its state, animate the change) or a new one (create fresh state, run insertion and removal transitions). It has two sources of identity. Structural identity comes from where a view sits in the code: its position in the view tree, including which branch of an if. Explicit identity is one you give it: .id(value), or the id of an element in ForEach and List.

The consequences are practical. An if cond { A } else { B } produces a _ConditionalContent<A, B>; switching the branch removes one view and inserts another, so state in the old branch is lost and a transition plays. A ternary that changes only a value (Text(on ? "A" : "B"), or .opacity(on ? 1 : 0)) keeps the same view and just updates it, preserving state and animating smoothly. So prefer changing modifiers and values over swapping branches when you want continuity. Changing an .id(…) deliberately resets the view: a way to restart state or an animation. For collections, ids must be stable and unique (a database key or UUID); using the array index or a value that changes makes SwiftUI believe every row is new. AnyView hides the type and defeats structural identity, so avoid it unless needed.

An if/else is a conditional with two possible branches, so a change is a removal and an insertion. A ternary is always one Text, just with a different value.

IdentityState on changeAnimation
if on { A } else { B }structural: different branchold view removed, new insertedtransition (fade by default)
Text(on ? "A" : "B")structural: same viewkeptvalue change, animates in place
.id(token) changesexplicitresetold out, new in

Common traps

  • Using ForEach(array.indices) or an unstable id. Insertions and reorders then look like everything changing, losing state and breaking animations.
  • Switching an if branch to change a small detail. It destroys state; change a value or modifier instead.

They might follow up with

  • How do you deliberately reset a view’s state? Give it an .id that changes.
  • Why does AnyView hurt? It erases the type, so SwiftUI cannot rely on structure and diffs less efficiently.

Related questions

  • 8.1.1 What is a View, what does some View mean, and why do modifiers wrap rather than mutate?
  • 8.3.1 What do @State, @Binding and @Bindable do, and who owns the data?
  • 8.6.1 How do List, ForEach and the lazy stacks differ, and what do they need from your data?

← What is a View, what does some View mean, and why do modifiers wrap…