프로그래밍/iOS,macOS
UIPanGestureRecognizer 슬라이드 다운 뷰
chance
2021. 7. 1. 12:51
액션시트와 같이 하단에 표시되는 뷰를 pan gesture를 사용해 에니메이션 시키고, 사라지게 하는 예.
class DefaultTouchPanGestureRecognizer: UIPanGestureRecognizer {
var touchPosition: CGPoint?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
touchPosition = touches.first?.location(in: view)
}
}
class SampleViewController: UIViewController {
private var panGesture: DefaultTouchPanGestureRecognizer = .init()
private var firstPanPoint: CGPoint = .zero
let disposeBag: DisposeBag = .init()
let menuView: UIView = .init()
override func loadView() {
super.loadView()
panGesture.delegate = self
panGesture.rx
.event
.subscribe(onNext: { [weak self] gesture in
self?.panning(gesture: gesture)
})
.disposed(by: disposeBag)
}
}
delegate 설정. 가로방향은 필요 없으므로, 제외시킨다.
extension SampleViewController: UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
guard let view = touch.view else { return }
return (view is UIControl) == false
}
public func gestureRecognizerShouldBegan(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let panGestureRecognizer = gestureRecognizer as? DefaultTouchPanGestureRecognizer else { return true }
let velocity = panGestureRecognizer.velocity(in: panGestureRecognizer.view?.superview)
if abs(velocity.y) > abs(velocity.x) {
return true
} else {
return flase
}
}
}
pan 이벤트 처리
private func panning(gesture: UIPanGestureRecognizer) {
let point = gesture.translation(in: gesture.view?.superview)
if gesture.state == .began {
firstPanPoint = point
}
let height = menuView.frame.height
var offset: CGFloat = 0
let newHeight = max(0, height + (firstPanPoint.y - point.y))
if newHeight < height {
offset = height - newHeight
}
if gesture.state == .cancelled || gesture.state == .failed {
UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseOut], animations: { [weak self] in
self.menuView.transform = .identity
}, completion: nil)
} else if gesture.state == .ended {
let velocity = (0.2 * gesture.velocity(in: view).y)
var finalOffset = height - offset - velocity
if velocity > 500 {
finalOffset = 0
}
let duration: CGFloat = abs(velocity / 10_000) + 0.2
let animationDuration = TimeInterval(duration)
if finalOffset > (height / 2) {
UIView.animate(withDuration: animationDuration, delay:0, options: [.curveEaseOut], animations: { [weak self] in
self.menuView.transform = .identity
}, completion: nil)
} else {
UIView.animate(withDuration: animationDuration, delay: 0, options: [.curveEaseOut], animations: { [weak self] in
self?.menuView.transform = CGAffineTransform(translationX: 0, y: height)
self?.view.backgroundColor = .clear
}, completion: { [weak self] _ in
self?.dismiss(animated: false, completion: nil)
})
}
} else {
if offset > 0 {
menuView.transform = CGAffineTransform(translationX: 0, y: offset)
} else {
menuView.transform = .identity
}
}
}