SwiftUI text fade transition not animating in iOS 16+ - how to animate text content changes? (2026)

2 weeks ago 15
ARTICLE AD BOX

Updated for iOS 16+: The modern approach is to use .contentTransition(.opacity), which simplifies animating text content changes without needing the .id() modifier. The key is wrapping state changes in withAnimation to provide the animation context.

Modern Solution: Using .contentTransition (iOS 16+ / watchOS 9+)

For iOS 16+, use .contentTransition(.opacity) - this is the recommended modern approach that doesn't require the .id() modifier:

struct TextAnimationView: View { @State private var textValue = "Hello" var body: some View { VStack(spacing: 50) { Text(textValue) .font(.largeTitle) .frame(width: 200, height: 200) .contentTransition(.opacity) Button("Change Text") { withAnimation(.easeInOut(duration: 1.0)) { textValue = textValue == "Hello" ? "World" : "Hello" } } } } }

Key point: .contentTransition handles content changes within the same view, so you don't need .id() to force view identity changes.

Legacy Solution: Using .transition with .id() (iOS 13-15)

For older iOS versions (13-15), use .transition(.opacity) with .id(). Note: This is the approach from older StackOverflow answers, but .contentTransition is preferred for iOS 16+:

Text(textValue) .id("MyText" + textValue) .transition(.opacity)

Then wrap the state change in withAnimation:

Button("Change Text") { withAnimation(.easeInOut(duration: 1.0)) { textValue = textValue == "Hello" ? "World" : "Hello" } }

Complete Example: Cycling Text

Here's a minimal example that cycles through text values:

struct CyclingText: View { @State private var currentIndex = 0 @State private var timer: Timer? private let texts = ["First", "Second", "Third"] var body: some View { Text(texts[currentIndex]) .font(.largeTitle) .contentTransition(.opacity) .onAppear { startCycling() } .onDisappear { timer?.invalidate() } } private func startCycling() { timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { _ in withAnimation(.easeInOut(duration: 1.0)) { currentIndex = (currentIndex + 1) % texts.count } } } }

The key is wrapping the state change in withAnimation - this provides the animation context that both .transition() and .contentTransition() need to animate.

Summary for iOS 16+: Use .contentTransition(.opacity) with withAnimation - it's simpler and doesn't require managing .id() modifiers.

Read Entire Article