iosswiftswiftui

SwiftUI drag gesture and contentTransition weird behaviour (jumping/stutter) when dragging


enter image description here

I'm trying to build a view which can be swiped up and down. I'm not sure why the above behaviour is occurring in my code. The text is kind of jumping/stuttering. I want the view to be draggable while the text is animating without the animation being broken. Am I doing something I'm not supposed to do? Below is my code. Any tips would be highly appreciated.

struct ContentView: View {
    @State var offset : CGFloat = 0
    @State var time: String = "0.0"
    let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()

    var body: some View {
      VStack {
        Spacer()
        Text(String(time)).contentTransition(.numericText())
          .onReceive(timer) { value in
            withAnimation {
              time = value.timeIntervalSince1970.minuteSecond // I have an extension for this.
            }
          }
        Spacer()
      }
      .frame(maxWidth: .infinity)
      .background(.red)
      .offset(y: offset)
      .gesture(swipeDownGesture)
    }
}

extension ContentView {
  var swipeDownGesture: some Gesture {
    DragGesture()
      .onChanged(onDrag(value:))
      .onEnded(onSwipeDownEnded(value:))
  }

  func onDrag(value: DragGesture.Value){
      let horizontalAmount = value.translation.width
      let verticalAmount = value.translation.height
      let isHorizontalSwipe = abs(horizontalAmount) > abs(verticalAmount)
      let isSwipeDown = verticalAmount > 0
      if isHorizontalSwipe {
        let isSwipeRight = horizontalAmount > 0
        if isSwipeRight {
          // Ignore
        } else {
          // Ignore
        }
      }
      else if isSwipeDown {
        withAnimation {
          offset = value.translation.height
        }
      }
  }

  func onSwipeDownEnded(value: DragGesture.Value){
    withAnimation(.smooth(duration: 0.32, extraBounce: 0.22)) {
      offset = 0
    }
  }
}

Solution

  • It seems that the contentTransition doesn't work well in combination with changes to the position of the text on the screen.

    iOS 17 and above

    It helps to add .geometryGroup() to the Text:

    Text(String(time)).contentTransition(.numericText())
        .onReceive(timer) { value in
            // as before
        }
        .geometryGroup() // 👈 HERE
    

    Animation

    Earlier iOS versions

    The modifier .drawingGroup() also works. However, this modifier is more likely to cause side effects on other aspects of the display, so you probably want to use with caution.