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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 import UIKitfinal 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) } } }