SwiftUI Gestures

Episode #400 | 14 minutes | published on July 12, 2019 | Uses Xcode-11.0-beta3, Swift-5.1
Free Video
Attaching gestures works quite a bit differently in SwiftUI than in UIKit. In this episode we will look at the @DragGesture property wrapper and how we can use gestures to update custom state that we can then use to transform our UI.

Adding Drag Gesture

We will start by defining the state for our drag gesture. We will define the active and inactive state along with the translation amount while dragging.

enum DragInfo {
    case inactive
    case active(translation: CGSize)

    var translation: CGSize {
        switch self {
        case .inactive:
            return .zero
        case .active(let t):
            return t
        }
    }

    var isActive: Bool {
        switch self {
        case .inactive: return false
        case .active: return true
        }
    }
}

Defining Gesture State

Next we will define the gesture state parameter for the drag gesture. We will also create a gesture to update the property and attach this gesture to the last card view along with the offset set using the translation amount. Next we will inspect the drag gesture and choose a value for .offset and .scaleEffect. Since we now have more than one statement, we have to explicitly add the return keyword with our ZStack.

@GestureState var dragInfo = DragInfo.inactive

var body: some View {
    let gesture = DragGesture()
        .updating($dragInfo) { (value, dragInfo, _) in
            dragInfo = .active(translation: value.translation)
        }

    return ZStack {
        CardView(title: "Walmart", color: .blue)
            .offset(x: 0, y: dragInfo.isActive ? -400 : -40)
            .scaleEffect(dragInfo.isActive ? 1 : 0.90)
        CardView(title: "Target", color: .red)
            .offset(x: 0, y: dragInfo.isActive ? -200 : -20)
            .scaleEffect(dragInfo.isActive ? 1 : 0.95)
        CardView(title: " Card", color: .black)
            .offset(dragInfo.translation)
            .gesture(gesture)
    }
    .offset(x: 0, y: 80)
    .animation(.spring(mass: 1.0, stiffness: 100, damping: 20, initialVelocity: 0))
}

Adding Rotation Effect

Currently, the cards are slightly up due to the .offset, we will rotate these cards by setting the angle and axis for the 3D effect. Note that we are inspecting the gesture while choosing the value for angle dynamically.

To coordinate the rotation and translation movement for the top card, we will rotate the card on the 2D plane and set the angle using the width of the translation obtained from the gesture.

To make the top card move up and down we will set the .rotation3DEffect using the height of translation along the x-axis.

@GestureState var dragInfo = DragInfo.inactive

var body: some View {
    let gesture = DragGesture()
        .updating($dragInfo) { (value, dragInfo, _) in
            dragInfo = .active(translation: value.translation)
        }

    return ZStack {
        CardView(title: "Walmart", color: .blue)
            .offset(x: 0, y: dragInfo.isActive ? -400 : -40)
            .rotation3DEffect(Angle(degrees: dragInfo.isActive ? 20 : 4), axis: (x: 0, y: 0.25, z: 1))
            .scaleEffect(dragInfo.isActive ? 1 : 0.90)
        CardView(title: "Target", color: .red)
            .offset(x: 0, y: dragInfo.isActive ? -200 : -20)
            .rotation3DEffect(Angle(degrees: dragInfo.isActive ? 10 : 2), axis: (x: 0, y: 0.25, z: 1))
            .scaleEffect(dragInfo.isActive ? 1 : 0.95)
        CardView(title: " Card", color: .black)
            .offset(dragInfo.translation)
            .rotationEffect(Angle(degrees: Double(dragInfo.translation.width / 10)))
            .rotation3DEffect(Angle(degrees: Double(dragInfo.translation.height / 50)), axis: (x: 1, y: 0, z: 0))
            .gesture(gesture)
    }
    .offset(x: 0, y: 80)
    .animation(.spring(mass: 1.0, stiffness: 100, damping: 20, initialVelocity: 0))
}
blog comments powered by Disqus