Structural identity is how SwiftUI recognizes a view by its position in the layout description,
without an explicit ID. Branching with if gives the two branches different identities, so
SwiftUI destroys and recreates views when the condition flips. Keeping identity stable preserves
state and avoids surprise animations.
Branches Create New Identities
An if let in a body compiles to _ConditionalContent, one identity per branch. When the
condition changes, SwiftUI tears one view down and builds the other, deallocating the old view's
state and animating the swap.
struct UserView: View {
let user: User?
var body: some View {
if let user = user {
LoggedUser(user: user)
} else {
AnonymousUserView()
}
}
}
print(Mirror(reflecting: UserView(user: nil).body))
// Mirror for _ConditionalContent<LoggedUser, AnonymousUserView>
That is fine when the branches are genuinely different views. It is a bug factory when both branches hold the same view:
// Loses ComplexView's state on every toggle, and animates the swap.
if isEnabled {
ComplexView()
} else {
ComplexView()
.disabled(true)
}
Inline the Condition into the Modifier
Move the condition inside the modifier value and the branch disappears. SwiftUI sees one view whose modifier value changed, so state survives and nothing is recreated.
struct AchievementView: View {
let isEnabled: Bool
var body: some View {
ComplexView()
.disabled(isEnabled ? false : true)
}
}
The rule: branch with if or switch only to present genuinely different views. Otherwise keep
one view and vary its modifiers.
Inert Modifiers Are Free
Some modifiers cost nothing at their neutral value: padding(0) and opacity(1) change nothing
in the view tree, and SwiftUI skips them. That makes them safe to apply unconditionally with a
ternary.
ComplexView()
.opacity(isEnabled ? 1 : 0)
.padding(isEnabled ? 8 : 0)
This is the performant way to show, hide, and adjust a view without touching its identity.