SwiftUI Transition not happening
Asked Answered
E

2

5

I am new to SwiftUI and I am trying to use the .transition, but for some reason no transition happens.

You can see the code below:

View

import SwiftUI

struct ContentView: View {
  @ObservedObject var viewModel = ViewModel()
  
  var body: some View {
    if self.viewModel.model.show {
      Text("Showing")
        .padding()
    } else {
      Text("Not Showing")
        .padding()
        .transition(.asymmetric(insertion: .scale, removal: .opacity))
    }
    
    Button {
      self.viewModel.show()
    } label: {
      Text("Tap to change")
    }
  }
}

ViewModel

class ViewModel: ObservableObject {
  @Published private(set) var model = Model()
  
  func show() {
    self.model.toggleShow()
  }
}

Model

struct Model {
  var show: Bool = true
  
  mutating func toggleShow() {
    self.show.toggle()
  }
}

When I tap the button the text changes but no transition occurs.

I feel like I am missing something trivial here.

Can anyone please assist?

Edouard answered 20/3, 2022 at 12:4 Comment(0)
F
13

You need an animation (to animate transition) and a container (which performs actual transition, because default implicit Group does not do that).

Here is fixed part of code (tested with Xcode 13.2 / iOS 15.2)

*Note:Preview > Debug > Slow Animation for better visibility

demo

var body: some View {
    VStack {                            // << this !!
        if self.viewModel.model.show {
            Text("Showing")
                .padding()
        } else {
            Text("Not Showing")
                .padding()
                .transition(.asymmetric(insertion: .scale, removal: .opacity))
        }
    }
    .animation(.default, value: self.viewModel.model.show)  // << here !!

    Button {
        self.viewModel.show()
    } label: {
        Text("Tap to change")
    }
}
Fungoid answered 20/3, 2022 at 12:28 Comment(1)
the problem here is that you are applying .animation to the entire sub-hierarchy. This means that if the sub-hierarchy contains other changes that happen in the same transaction as the transition, then all of these changes will inherit the animation which you intended exclusively for the transition. To fix this, you would need the new iOS 17+ .animation {} modifier, but this one would need to be applied at the level where the transition is set, which breaks the animation again (since the animation itself needs to be at the container level).Ashok
S
1

Your code is fine (besides the fact that you need a VStack wrapping the text and the button), you only need to tell SwiftUI to use the transition by wrapping the command inside withAnimation().

Here's what you simply need to do in ContentView (look at the Button):

    @ObservedObject var viewModel = ViewModel()
    
    var body: some View {
        VStack {
            if self.viewModel.model.show {
                Text("Showing")
                    .padding()
            } else {
                Text("Not Showing")
                    .padding()
                    .transition(.asymmetric(insertion: .scale, removal: .opacity))
            }
            
            
            Button {
                withAnimation {       // This is what you need to trigger the transition
                    self.viewModel.show()
                }
            } label: {
                Text("Tap to change")
            }
        }
        .animation(.easeIn, value: self.viewModel.show)

    }
Slab answered 20/3, 2022 at 15:39 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.