Issue #301
Make it more composable using UIViewController
subclass and ThroughView
to pass hit events to underlying views.
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
}
}