Controlling animation dynamically

I have rewatched point free's episode on animation.

I can't figure out a way to turn animation on or off on a scheduler.

For example in the following code, I would like to animate the changes only after the first update.

environment.provider
  .fetchedResults(for: state.id, limit: limit, on: environment.backgroundQueue)
  .receive(on: environment.mainQueue.animation())
  .map(ItemAction.updateItems)
  .eraseToEffect()
 .cancellable(id: fetchedItemsCancelID!, cancelInFlight: true)

Can you determine in state if the animation should run or not?

.receive(
  on: environment.mainQueue.animation(
    state.hasUpdated ? nil : .default
  )
)

Oh thanks! I should have thought of that...

It would be nice if we could control the animation from the publisher or from the state and disable the animation when there is a lot of updates for example.

If you're using SwiftUI, sending many animations requests shouldn't be an issue, as the framework is particularly efficient at handling this configuration. If your state is complex to diff, you can always debounce your effect for a few milliseconds to filter high rate results. In this case, you can use the same animated scheduler to debounce and replace the .receive step.

By the way, if you want to disable animations for the first appearance, you should probably use the .disablesAnimations flag of Transaction instead of changing the animation value. This will prevent other animations to be added to the transaction during the transition.

On a related topic, I've proposed a few weeks ago a solution to control animations from the View's side. I'll probably extract it as a standalone library if animations in TCA shouldn't see their stories unified.

1 Like

Thanks that was really helpful.

I have a similar problem again in my TabView:

     TabView(
      selection:
        viewStore.binding(
          get: { $0.selection },
          send: { .didSelectItem(id: $0) }
        )
    ) {
      ForEach(viewStore.subItemIDs, id: \.self) { id in
        IfLetStore(
          store.scope(
            state: { $0.subitems[id: id] },
            action: { .item(id: id, action: $0) }
          )
        ) { eachStore in
          NavigationView {
            VCollection(store: eachStore)
          }
          .tabItem {
            WithViewStore(eachStore.scope(state: EachViewState.init)) { eachViewStore in
              Label(eachViewStore.title, systemImage: eachViewStore.symbol)
                .environment(\.symbolVariants, .none)
            }
          }
          .tag(id)
        }
        .introspectNavigationController {
          with($0.navigationBar, Appearance.opaqueNavigationBarStyle)
        }
      }
    }
    .introspectTabBarController { with($0.tabBar, Appearance.baseTabBarStyle) }
  }

This seems to work fine... how ever if on one of the tab I navigate a couple of times down the state using navigation links.

When I tap another tab and come back... It will recreate the view and animate the navigation to the current item on that tab.

I can't figure out a way to disable animation when the tabView selection is change.