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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
| class PanViewController: UIViewController { var animator = UIViewPropertyAnimator(duration: 0, curve: .easeOut) lazy var panGR = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_: ))) let slideView = UIView() let gripView = UIView() var options: Options = Options() var didHide: (() -> Void)? let pullDownVelocity: CGFloat = 70
class Options { var contentView: UIView = UIView() var percentHeight: CGFloat = 0.24 }
override func loadView() { view = ThroughView() view.translatesAutoresizingMaskIntoConstraints = false }
override func viewDidLoad() { super.viewDidLoad()
setup() }
func setup() { slideView.layer.cornerRadius = 10 slideView.clipsToBounds = true
gripView.backgroundColor = UIColor.yellow gripView.layer.cornerRadius = 1
view.addSubview(slideView) slideView.addSubview(gripView) slideView.addGestureRecognizer(panGR)
NSLayoutConstraint.on([ slideView.leftAnchor.constraint(equalTo: view.leftAnchor), slideView.rightAnchor.constraint(equalTo: view.rightAnchor), slideView.heightAnchor.constraint(equalTo: view.heightAnchor), slideView.topAnchor.constraint(equalTo: view.bottomAnchor) ])
NSLayoutConstraint.on([ gripView.centerXAnchor.constraint(equalTo: slideView.centerXAnchor), gripView.topAnchor.constraint(equalTo: slideView.topAnchor, constant: 16), gripView.widthAnchor.constraint(equalToConstant: 30), gripView.heightAnchor.constraint(equalToConstant: 2) ]) }
func apply(options: Options) { self.options.contentView.removeFromSuperview() slideView.insertSubview(options.contentView, at: 0)
NSLayoutConstraint.on([ options.contentView.leftAnchor.constraint(equalTo: slideView.leftAnchor), options.contentView.rightAnchor.constraint(equalTo: slideView.rightAnchor), options.contentView.topAnchor.constraint(equalTo: slideView.topAnchor), options.contentView.heightAnchor.constraint(equalTo: slideView.heightAnchor, multiplier: options.percentHeight) ])
self.options = options }
@objc func handlePan(_ gr: UIPanGestureRecognizer) { switch gr.state { case .began: break case .changed: break case .ended: let velocity = gr.velocity(in: slideView) if velocity.y > pullDownVelocity { hide() } default: break } }
func show() { guard let parentView = view.superview else { return }
animator = self.makeAnimator() animator.addAnimations { self.slideView.transform = CGAffineTransform( translationX: 0, y: -parentView.bounds.height * self.options.percentHeight - parentView.safeAreaInsets.bottom ) }
animator.startAnimation() }
func hide() { animator = self.makeAnimator() animator.addAnimations { self.slideView.transform = CGAffineTransform.identity }
animator.addCompletion({ _ in self.didHide?() })
animator.startAnimation() }
func makeAnimator() -> UIViewPropertyAnimator { return UIViewPropertyAnimator(duration: 0.25, dampingRatio: 1.0) } }
class ThroughView: UIView { override func didMoveToSuperview() { super.didMoveToSuperview()
guard let superview = superview else { return }
NSLayoutConstraint.on([pinEdges(view: superview)]) }
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { guard let slideView = subviews.first else { return false }
return slideView.hitTest(convert(point, to: slideView), with: event) != nil } }
|