Gesture & Keyboard

๐Ÿ‘† ์ œ์Šค์ฒ˜ & ํ‚ค๋ณด๋“œ RxCocoa ํŒจํ„ด ๊ฐ€์ด๋“œ

โ€œUIKit ์ด๋ฒคํŠธ๋ฅผ ์„ ์–ธํ˜•์œผ๋กœ ๋‹ค๋ฃจ์ž.โ€

์ด ๋ฌธ์„œ์—์„œ๋Š” **์ œ์Šค์ฒ˜ ์ธ์‹๊ธฐ(Gesture Recognizer)**์™€ ํ‚ค๋ณด๋“œ ๋…ธํ‹ฐํ”ผ์ผ€์ด์…˜์„ RxCocoa๋กœ ์šฐ์•„ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ•œ๊ตญ์–ด๋กœ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.


1๏ธโƒฃ UITapGestureRecognizer ๋ฐ”์ธ๋”ฉ

let tap = UITapGestureRecognizer()
view.addGestureRecognizer(tap)

tap.rx.event
    .withUnretained(self)
    .subscribe(onNext: { vc, _ in
        vc.view.endEditing(true) // ํƒญ ์‹œ ํ‚ค๋ณด๋“œ ๋‚ด๋ฆฌ๊ธฐ
    })
    .disposed(by: bag)
  • rx.event๋Š” Observable<UITapGestureRecognizer>

  • withUnretained๋กœ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€

  • ๋™์ผ ํŒจํ„ด์œผ๋กœ UISwipeGestureRecognizer, UIPanGestureRecognizer ํ™œ์šฉ ๊ฐ€๋Šฅ

ํ•œ ๋ฒˆ๋งŒ ๋™์ž‘์‹œํ‚ค๊ธฐ

tap.rx.event
    .take(1)
    .bind(to: viewModel.firstTap)
    .disposed(by: bag)

2๏ธโƒฃ RxGesture ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (์„ ํƒ)

  • RxGesture๋Š” view.rx.tapGesture() ๋“ฑ ๊ฐ„๋‹จ ๋ฌธ๋ฒ• ์ œ๊ณต

  • ์˜ˆ์‹œ:

    view.rx.tapGesture()
        .when(.recognized)
        .subscribe(onNext: { _ in print("Tapped!") })
        .disposed(by: bag)

3๏ธโƒฃ ํ‚ค๋ณด๋“œ ๋†’์ด ์ŠคํŠธ๋ฆผํ™”

extension Reactive where Base: UIView {
    var keyboardHeight: Observable<CGFloat> {
        let willShow = NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
            .map { ($0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0 }

        let willHide = NotificationCenter.default.rx.notification(UIResponder.keyboardWillHideNotification)
            .map { _ in CGFloat(0) }

        return Observable.merge(willShow, willHide)
            .distinctUntilChanged()
    }
}

์‚ฌ์šฉ ์˜ˆ

view.rx.keyboardHeight
    .observe(on: MainScheduler.instance)
    .subscribe(onNext: { [weak self] height in
        self?.bottomConstraint.constant = height
        self?.view.layoutIfNeeded()
    })
    .disposed(by: bag)

4๏ธโƒฃ ํ‚ค๋ณด๋“œ ์•ˆ์ „์˜์—ญ ์• ๋‹ˆ๋ฉ”์ด์…˜

view.rx.keyboardHeight
    .withLatestFrom(view.rx.layoutIfNeeded(), resultSelector: { h, _ in h })
    .bind(to: scrollView.rx.keyboardAnimatedInsets)
    .disposed(by: bag)

keyboardAnimatedInsets๋Š” ์ปค์Šคํ…€ Extension์œผ๋กœ, ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ณก์„ ์„ ๋…ธํ‹ฐ์—์„œ ์ถ”์ถœํ•ด ์‚ฌ์šฉ


5๏ธโƒฃ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ & ์ฃผ์˜์‚ฌํ•ญ

ํ•ญ๋ชฉ
์ฃผ์˜์ 

์ œ์Šค์ฒ˜ ์ธ์‹๊ธฐ

View deinit ์‹œ gesture.view retain ํ™•์ธ

ํ‚ค๋ณด๋“œ ๋…ธํ‹ฐ

.takeUntil(self.rx.deallocated)๋กœ ์ž๋™ ํ•ด์ œ ๊ฐ€๋Šฅ

RxGesture

when(.recognized) ํ•„ํ„ฐ๋ง ์•ˆ ํ•˜๋ฉด ์ค‘๋ณต ์ด๋ฒคํŠธ ๋ฐœ์ƒ


6๏ธโƒฃ Mini Quiz

  1. ๋‘ ์†๊ฐ€๋ฝ ํƒญ ์ œ์Šค์ฒ˜๋ฅผ Rx๋กœ ๋งŒ๋“ค๊ณ , ์ฒซ ์ธ์‹ ํ›„ ์ž๋™ ํ•ด์ œํ•˜๋ ค๋ฉด?

  2. ํ‚ค๋ณด๋“œ ๋†’์ด ์ŠคํŠธ๋ฆผ์—์„œ ๋†’์ด๊ฐ€ ๋™์ผํ•ด๋„ ์ค‘๋ณต ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ• ๊นŒ?

  3. RxGesture ๋Œ€์‹  ๊ธฐ๋ณธ rx.event๋ฅผ ์จ์•ผ ํ•˜๋Š” ์ƒํ™ฉ ํ•œ ๊ฐ€์ง€๋Š”?

Answers
  1. let tap2 = UITapGestureRecognizer()
    tap2.numberOfTouchesRequired = 2
    view.addGestureRecognizer(tap2)
    
    tap2.rx.event
        .take(1)
        .subscribe(onNext: { _ in print("double finger tap") })
        .disposed(by: bag)
  2. distinctUntilChanged() ์˜คํผ๋ ˆ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋™์ผ ๊ฐ’ ํ•„ํ„ฐ๋ง

  3. RxGesture ๋ฏธ์‚ฌ์šฉ ํ”„๋กœ์ ํŠธ(์˜์กด์„ฑ ์ตœ์†Œํ™”) ๋˜๋Š” ์ปค์Šคํ…€ ์ƒํƒœ(.began, .changed) ๋“ฑ ์„ธ๋ฐ€ํ•œ ์ œ์–ด ํ•„์š”ํ•  ๋•Œ


๐ŸŽ‰ Operators ์ฑ•ํ„ฐ ๋! ์ด์ œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์ž‘์„ฑ์œผ๋กœ ํ•™์Šต์„ ๊ฐ•ํ™”ํ•ด ๋ณด์„ธ์š”. ๐Ÿš€

Last updated