2

I am learning SwiftUI as a hobby and I am trying to create a draggable rectangular view using @GestureState as shown here. When the view is being dragged I want to animate a change in corner and shadow radius. The example in Apple Documentation uses implicit animations for the shadow part and removes any animations above the offset modifier. However, because I also want to animate the corner radius, I think I need to do it explicitly. This is my first try:

struct DraggableView: View {
enum DragState {
    case inactive
    case pressing
    case dragging(translation: CGSize)
    
    var translation: CGSize {
        switch self {
        case .inactive, .pressing:
            return .zero
        case .dragging(let translation):
            return translation
        }
    }
    
    var isActive: Bool {
        switch self {
        case .inactive:
            return false
        case .pressing, .dragging:
            return true
        }
    }
    
    var isDragging: Bool {
        switch self {
        case .inactive, .pressing:
            return false
        case .dragging:
            return true
        }
    }
}

@GestureState var dragState = DragState.inactive

var body: some View {
    let minDuration = 0.5
    let longPressThenDrag = LongPressGesture(minimumDuration: minDuration)
        .sequenced(before: DragGesture())
        .updating($dragState) { value, state, transaction in
            transaction.animation = .easeInOut(duration: minDuration)
            switch value {
            case .first(true): // Long press begins
                state = .pressing
            case .second(true, let drag): // Dragging begins
                transaction.animation = nil // Do not animate translation update
                state = .dragging(translation: drag?.translation ?? .zero)
            default:
                state = .inactive
            }
        }
    
    return RoundedRectangle(cornerRadius: dragState.isActive ? 25 : 0)
        .frame(height: 150)
        .foregroundColor(.blue)
        .offset(
            x: dragState.translation.width,
            y: dragState.translation.height
        )
        .shadow(radius: dragState.isActive ? 25 : 0)
        .gesture(longPressThenDrag)
}

}

Unfortunately there are two issues I am stuck with.

  1. When the dragState is set back to the initial .inactive value the changes are not animated:

Screen recording: dragState reset

  1. When the gesture sequence is interrupted abruptly, the shadow behaves strangely:

Screen recording: interrupted gesture

Any ideas how to fix this? Thank you in advance.

1 Answers1

2

I ended up using conditionally set implicit animations like this:

return RoundedRectangle(cornerRadius: dragState.isActive ? 25 : 0)
    .frame(height: 150)
    .foregroundColor(.blue)
    .offset(
        x: dragState.translation.width,
        y: dragState.translation.height
    )
    .shadow(radius: dragState.isActive ? 25 : 0)
    .animation(dragState.isDragging ? nil : .easeInOut(duration: minDuration))
    .gesture(longPressThenDrag)

It solves the two issues. But if anyone can show me how to do it explicitly I'd still like to know.

  • I guess you can do it now with ```.onChange``` modifier in iOS 14. Let me know if you don't know how to use it, I'd add a solution then. – leonboe1 Oct 25 '20 at 11:24
  • 1
    @leonboe1 Thank you for the suggestion. I am now using that approach with `.onChange` already in some places. I also learned recently that I could bind an animation to a certain value change with `animation(Animation?, value: V)` which makes much easier to set different implicit animations. – seriouslysupersonic Nov 09 '20 at 12:00