본문 바로가기
iOS/RxSwift

[RxSwift] RxGesture로 바텀시트 Gesture 처리하기

by 0inn 2022. 11. 29.

어제 발자국 프로젝트 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