Issue #475
For a snack bar or image viewing, it’s handy to be able to just flick or toss to dismiss
We can use UIKit Dynamic, which was introduced in iOS 7, to make this happen.
Use UIPanGestureRecognizer
to drag view around, UISnapBehavior
to make view snap back to center if velocity is low, and UIPushBehavior
to throw view in the direction of the gesture.
import UIKit
final class FlickHandler {
private let viewToMove: UIView
private let referenceView: UIView
private var panGR: UIPanGestureRecognizer!
private let animator: UIDynamicAnimator
private var snapBehavior: UISnapBehavior?
private var pushBehavior: UIPushBehavior?
private let debouncer = Debouncer(delay: 0.5)
var onFlick: () -> Void = {}
init(viewToMove: UIView, referenceView: UIView) {
self.viewToMove = viewToMove
self.referenceView = referenceView
self.animator = UIDynamicAnimator(referenceView: referenceView)
self.panGR = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(_:)))
viewToMove.addGestureRecognizer(panGR)
}
@objc private func handleGesture(_ gr: UIPanGestureRecognizer) {
switch gr.state {
case .began:
handleBegin()
case .changed:
handleChange(gr)
default:
handleEnd(gr)
}
}
private func handleBegin() {
animator.removeAllBehaviors()
}
private func handleChange(_ gr: UIPanGestureRecognizer) {
let translation = panGR.translation(in: referenceView)
viewToMove.transform = CGAffineTransform(
translationX: translation.x,
y: translation.y
)
}
private func handleEnd(_ gr: UIPanGestureRecognizer) {
let velocity = gr.velocity(in: gr.view)
let magnitude = sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y))
if magnitude > 1000 {
animator.removeAllBehaviors()
let pushBehavior = UIPushBehavior(items: [viewToMove], mode: .instantaneous)
pushBehavior.pushDirection = CGVector(dx: velocity.x, dy: velocity.y)
pushBehavior.magnitude = magnitude / 35
self.pushBehavior = pushBehavior
animator.addBehavior(pushBehavior)
onFlick()
debouncer.run { [weak self] in
self?.animator.removeAllBehaviors()
}
} else {
let snapBehavior = UISnapBehavior(
item: viewToMove,
snapTo: viewToMove.center
)
self.snapBehavior = snapBehavior
animator.addBehavior(snapBehavior)
}
}
}