Animation only works with deprecated modifier

Hello all!

In my project, I have created a swipeable carousel view that changes a published property (currentHoleIdx) in my ViewModel that I use to programmatically change a TabView.

I want the TabView to animate when the index changes, and it does, but only with the deprecated .animated(.easeInOut). I have tried .animation(.easeInOut, value:model.currentHoleIdx) and also tried withAnimation when I change the currentHoleIdx property. I will do my best to post the code to show what I have done. Thanks in advance for any help!

TabView with animation

 VStack {

      ZStack(alignment: .top) {
        // Mini Scorecard
        MiniScorecard(scorecard:model.scorecards[model.currentScorecardIdx!], teams: model.scorecards[model.currentScorecardIdx!].teams)
          .coordinateSpace(name: "mini")
//          .opacity(showScore ? 1 : 0)
          .background(Color("card"))
          .background(
            GeometryReader { geo in
              Color.clear.onAppear {
                showScoreOffset = geo.size.height
                // print(geo.size.height)
              }
            }
          )
           

       
         
        // Scorecard TabView
        TabView(selection: $model.currentHoleIdx,
            content: {
              ForEach(0..<model.scorecards[model.currentScorecardIdx!].holes.count) {idx in
                let scorecard = model.scorecards[model.currentScorecardIdx!]
                 
                if scorecard.formatType == "individual" {
                   
                  if scorecard.gameID == "Standard" {
                    IndivStandardScoreScrollview(idx:idx)
                      .tag(idx)
                      .animation(.easeInOut, value: model.currentHoleIdx) // // does not work does not work with idx or model.currentHoleIdx
                       
                  }
                } else {
                   
                  if scorecard.gameID == "Standard" {
                    TeamStandardScoreScrollview(holeIdx:idx)
                      .tag(idx)
                      .animation(.easeInOut, value: idx) // does not work does not work with idx or model.currentHoleIdx
                       
                  }
                }
              }
            Color.clear
              .tag(model.scorecards[model.currentScorecardIdx!].holes.count)

            }).tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
          .background(Color("card"))
          .offset(y:showScore ? showScoreOffset : 0)
          .animation(.easeInOut, value: showScore)
          .animation(.easeInOut, value: scorecardOffset)
//          .animation(.easeInOut, value: model.currentHoleIdx) // does not work
      }.frame(width:UIScreen.main.bounds.width - 30)
    }
    .offset(y: scorecardOffset)
    .offset(y: model.currentHoleIdx < model.scorecards[model.currentScorecardIdx!].holes.count ? 0 : scorecardHeight)
    .padding(.top)
    .padding(.horizontal, 15)
    .animation(.easeInOut, value: scorecardOffset)
    .animation(.easeInOut, value: model.currentHoleIdx) // does not work!!!
//    .animation(.easeInOut) // Only thing that works! when model.currentHoleIdx changes from drag gesture
    .overlay(
      GeometryReader { geo in
        Color.clear.onAppear {
          scorecardHeight = geo.size.height
        }
      }
    )

Swipeable carousel drag gesture - withAnimation when interacting with model.currentHoleIdx

return HStack(alignment: .center, spacing: spacing) {
      items
    }
    .offset(x: CGFloat(calcOffset), y: 0)
    .gesture(DragGesture(minimumDistance: 0, coordinateSpace: .named("canvas")).updating($screenDrag) { dragValue, gestureState, transaction in
//      self.model.screenDrag = Float(currentState.translation.width)
       
      // gestureState == screenDrag
      gestureState = dragValue.translation
       
    }.onEnded { value in
      // screenDrag = 0 - Don't need this, bc GestureState automatically resets back to initial value
       
      // Swipe to next hole
      if (value.translation.width < -15) && self.model.currentHoleIdx < Int(numberOfItems) - 1 {
         
        // calculate currentholeidx based on swipe width and cardwidth
        let calculatedHoleIdx = self.model.currentHoleIdx - Int(floor(value.translation.width/(cardWidth + spacing + 10)))
         
        withAnimation { // withAnimation Here
          self.model.currentHoleIdx = calculatedHoleIdx > Int(numberOfItems - 1) ? Int(numberOfItems - 1) : calculatedHoleIdx
        }

        let impactMed = UIImpactFeedbackGenerator(style: .medium)
        impactMed.impactOccurred()
      }
       
      // Swipe to previous hole
      if (value.translation.width > 15) && self.model.currentHoleIdx > 0 {
         
        let calculatedHoleIdx = self.model.currentHoleIdx - Int(ceil((value.translation.width/(cardWidth + spacing + 10))))
         
        withAnimation { // withAnimation Here
          self.model.currentHoleIdx = calculatedHoleIdx < 0 ? 0 : calculatedHoleIdx
        }
         
        self.model.currentHoleIdx = calculatedHoleIdx < 0 ? 0 : calculatedHoleIdx
         
        let impactMed = UIImpactFeedbackGenerator(style: .medium)
        impactMed.impactOccurred()
      }
       

I feel like i've tried every possible iteration of .animation and withAnimation. The only one that works is using the deprecated .animation() modifier. Hope to hear from someone soon. thanks again!

  • Can you show enough code to run and test? Hard to say something with just fragments of code.

  • Ahh, I'm not sure how to replicate the issue without sharing the whole code base.

  • I will add that the animation does work with a simple Text View but not with the IndivStandardScoreScrollview(idx:idx) or TeamStandardScoreScrollview(holeIdx:idx), which are Scrollviews. Does that have anything to do with it? Thanks for trying OOPer

Add a Comment