Insert, remove view with animation using transition + TCA

Hi
I'm trying to make an app which has card lists using TCA.
The cards can be removed or inserted by action.
But, not as MVVM, my cards transition methods doesn't work.

Here's my example

// App.swift

import SwiftUI
import ComposableArchitecture

struct AppState: Equatable {
  var cards: [Card] = (10...20).map { Card(id: $0, number: $0) }
}

enum AppAction {
  case cardCompleted(Int)
  case removeCard(Int)
  case getNewCards
}

struct AppEnvironment {
  var mainQueue: AnySchedulerOf<DispatchQueue> = DispatchQueue.main.eraseToAnyScheduler()
}

let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, env in
  switch action {
  case .cardCompleted(let id):
    guard let card = state.cards.filter({ $0.id == id }).first,
      let index = state.cards.firstIndex(of: card) else {
      return .none
    }
    state.cards[index].status = .completed
    return Effect.init(value: AppAction.removeCard(index))
      .delay(for: 2, scheduler: env.mainQueue)
      .eraseToEffect()
  case .removeCard(let index):
    state.cards.remove(at: index)
    return Effect.init(value: AppAction.getNewCards)
      .delay(for: 0.1, scheduler: env.mainQueue)
      .eraseToEffect()
  case .getNewCards:
    state.cards = (0...9).map { Card(id: $0, number: $0) }
    return .none
  }
}

struct AppView: View {
  let store: Store<AppState, AppAction>
  
  var body: some View {
    WithViewStore(self.store) { viewStore in
      ScrollView(.horizontal) {
        HStack(spacing: 20) {
          ForEach(viewStore.cards) { card in
            CardView(card: card)
              .onTapGesture {
                viewStore.send(.cardCompleted(card.id))
            }
          }
        }
      }
    }
  }
}
// Card.swift

import SwiftUI

enum CardStatus {
  case none
  case completed
}

struct Card: Equatable, Identifiable {
  var id: Int
  var number: Int
  var status: CardStatus = .none
}

struct CardView: View {
  let card: Card
  
  var body: some View {
    VStack {
      Text("Hello Card")
      Text("\(card.number)")
    }
    .frame(width: 100, height: 150)
    .background(Color.yellow)
    // this part doensn't work
    .transition(.asymmetric(
      insertion: AnyTransition.move(edge: .trailing),
      removal: AnyTransition.move(edge: .bottom)))
  }
}

struct CardView_Previews: PreviewProvider {
  static var previews: some View {
    CardView(card: Card(id: 0, number: 0))
  }
}

What I want is applying animation before removal and new card insertion after removal.

It would be nice if you let me know the solution and some knowledges about the relationship between SwiftUI and TCA(I mean, why my example didn't work).

Thanks for your help



I resolve this by adding .animation(.default).

Apple stopped animating list changes by default in Xcode 12, but as you saw, adding the view modifier (or sending an action that mutates the model in a withAnimation block) should fix things.

Thanks for your information!

I have a question.
As you said, if it was because of Xcode 12, then My original work with MVVM works fine with just transition.

Do you know why it didn't work with TCA?

Without seeing the code I'm not sure, but I've seen that SwiftUI list animations are off by default in Xcode 12, whether or not you use TCA or a vanilla SwiftUI view model.

Terms of Service

Privacy Policy

Cookie Policy