어제 발자국 프로젝트 BottomSheetVC를 구현했는데요 ! !
사실 너무 잘 짜여진 방식이 있어서 참고하여 구현했지만 . . 공부할 거랑 배울 점이 매우매우 많았습니다 . .
그 중 처음 사용해보기도 하고 너무 유용했던 "RxGesture"에 대해 간단하게 정리하겠습니다 ~ !
RxGesture
https://github.com/RxSwiftCommunity/RxGesture
GitHub - RxSwiftCommunity/RxGesture: RxSwift reactive wrapper for view gestures
RxSwift reactive wrapper for view gestures. Contribute to RxSwiftCommunity/RxGesture development by creating an account on GitHub.
github.com
기존 Gesture 객체들을 Rx로 감싸놓은 것으로 편리하게 제스처 구현이 가능합니다.
사용법
view.rx
.tapGesture()
.when(.recognized)
.subscribe(onNext: { _ in
//react to taps
})
.disposed(by: stepBag)
tap 제스처를 받은 코드입니다.
view.rx
.anyGesture(.tap(), .swipe([.up, .down]))
.when(.recognized)
.subscribe(onNext: { _ in
//dismiss presented photo
})
.disposed(by: stepBag)
여러 개의 제스처를 받을 수도 있습니다.
in iOS
view.rx.tapGesture() -> ControlEvent<UITapGestureRecognizer>
view.rx.pinchGesture() -> ControlEvent<UIPinchGestureRecognizer>
view.rx.swipeGesture(.left) -> ControlEvent<UISwipeGestureRecognizer>
view.rx.panGesture() -> ControlEvent<UIPanGestureRecognizer>
view.rx.longPressGesture() -> ControlEvent<UILongPressGestureRecognizer>
view.rx.rotationGesture() -> ControlEvent<UIRotationGestureRecognizer>
view.rx.screenEdgePanGesture() -> ControlEvent<UIScreenEdgePanGestureRecognizer>
view.rx.hoverGesture() -> ControlEvent<UIHoverGestureRecognizer>
view.rx.anyGesture(.tap(), ...) -> ControlEvent<UIGestureRecognizer>
view.rx.anyGesture(.pinch(), ...) -> ControlEvent<UIGestureRecognizer>
view.rx.anyGesture(.swipe(.left), ...) -> ControlEvent<UIGestureRecognizer>
view.rx.anyGesture(.pan(), ...) -> ControlEvent<UIGestureRecognizer>
view.rx.anyGesture(.longPress(), ...) -> ControlEvent<UIGestureRecognizer>
view.rx.anyGesture(.rotation(), ...) -> ControlEvent<UIGestureRecognizer>
view.rx.anyGesture(.screenEdgePan(), ...) -> ControlEvent<UIGestureRecognizer>
view.rx.anyGesture(.hover(), ...) -> ControlEvent<UIGestureRecognizer>
iOS에서 지원하는 RxGesture()입니다.
저는 바텀시트를 구현할 때, 해당 뷰를 터치하는 제스처와 뷰를 드래그하는 제스처가 필요했습니다.
그러므로 .tapGesture()와 .panGesture()를 사용하여 구현하였습니다.
Filtering State
기본적으로 gesture recoginzer의 state에는 filter가 존재하지 않습니다.
이 의미는 항상 초기 상태로 첫 번째 이벤트를 받게된다는 것입니다. (대부분 .possible)
Kind | States |
.tap() .click() .rightClick() .swipe() | .recognized |
.longPress() .press() | .began |
.pan() .pinch() .rotation() .magnification() .screenEdgePan() | .began .changed .ended |
.when() 연산자를 사용해서 상태를 필터링할 수 있습니다.
view.rx.tapGesture().when(.recognized)
view.rx.panGesture().when(.began, .changed, .ended)
view.rx
.anyGesture(.tap(), .swipe([.up, .down]))
.when(.recognized)
.subscribe(onNext: { gesture in
// Called whenever a tap, a swipe-up or a swipe-down is recognized (state == .recognized)
})
.disposed(by: bag)
view.rx
.anyGesture(
(.tap(), when: .recognized),
(.pan(), when: .ended)
)
.subscribe(onNext: { gesture in
// Called whenever:
// - a tap is recognized (state == .recognized)
// - or a pan is ended (state == .ended)
})
.disposed(by: bag)
Delegate customization
Lightweight customization
각각의 gesture들은 RxGestureRecogizerDelegate를 가집니다.
customize 또한 가능합니다.
- .always 는 true 반환
- .never 는 false 반환
- .custom 은 closure 실행
delegate method
beginPolicy -> gestureRecognizerShouldBegin(:_)
touchReceptionPolicy -> gestureRecognizer(_:shouldReceive:)
selfFailureRequirementPolicy -> gestureRecognizer(_:shouldBeRequiredToFailBy:)
otherFailureRequirementPolicy -> gestureRecognizer(_:shouldRequireFailureOf:)
simultaneousRecognitionPolicy -> gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)
eventRecognitionAttemptPolicy -> gestureRecognizer(_:shouldAttemptToRecognizeWith:) // macOS only
pressReceptionPolicy -> gestureRecognizer(_:shouldReceive:) // iOS only
custumozie 예시
view.rx.tapGesture(configuration: { gestureRecognizer, delegate in
delegate.simultaneousRecognitionPolicy = .always // (default value)
// or
delegate.simultaneousRecognitionPolicy = .never
// or
delegate.simultaneousRecognitionPolicy = .custom { gestureRecognizer, otherGestureRecognizer in
return otherGestureRecognizer is UIPanGestureRecognizer
}
delegate.otherFailureRequirementPolicy = .custom { gestureRecognizer, otherGestureRecognizer in
return otherGestureRecognizer is UILongPressGestureRecognizer
}
})
Full customization
default delegate를 자기 자신으로 만들거나 제거할 수 있습니다.
view.rx.tapGesture { [unowned self] gestureRecognizer, delegate in
gestureRecognizer.delegate = nil
// or
gestureRecognizer.delegate = self
}
처음 사용한 RxGesture였지만 사용법도 간단하고 너무 편하고 직관적이어서 좋았습니다 ! 끝 !
프로젝트 구현
bottomSheetView.rx.panGesture()
.withUnretained(self)
.bind { this, gesture in
let transition = gesture.translation(in: this.view)
// 위로 드래그하거나 fix인 경우 무시하는 로직
guard
transition.y > 0,
this.type == .drag else { return }
// 제스처 상태가 변하는 중일 때는 뷰도 변하게, 손 뗏을 때는 해당 위치에 따라 다른 동작 수행하도록
switch gesture.state {
case .changed:
this.bottomSheetView.transform = CGAffineTransform(translationX: 0, y: transition.y)
case .ended:
if transition.y < (this.bottomSheetView.bounds.height / 3) {
this.bottomSheetView.transform = .identity
} else {
this.dismiss(animated: true)
}
default:
break
}
}
.disposed(by: disposeBag)
backgroundView.rx.tapGesture()
.when(.recognized)
.withUnretained(self)
.bind { this, _ in
// 버튼 tap하면 사라지는 로직
this.dismiss(animated: true)
}
.disposed(by: disposeBag)
위 코드는 바텀 시트 구현한 곳에서 RxGesture를 사용한 부분입니다.
간단하게 주석으로 로직 설명해두었습니다 ㅎㅎ
처음 사용한 RxGesture였지만 사용법도 간단하고 너무 편하고 직관적이어서 좋았습니다 ! 끝 !
'iOS > RxSwift' 카테고리의 다른 글
[RxSwift] drive는 뭘까 (0) | 2022.12.18 |
---|---|
[RxSwift] Observable.create & Disposable.create (0) | 2022.11.22 |
[RxSwift] NotificationCenter, Notification (0) | 2022.11.21 |
[RxSwift] Combining Operators (0) | 2022.11.18 |
[RxSwift] Transforming Operators (0) | 2022.11.17 |