[RxSwift] - SearchBar 만들기

sun02·2022년 2월 22일
0

RxSwift

목록 보기
10/12

RxSwift를 사용해서 View의 subcomponent중 하나인
SearchBar를 만들어보겠습니다.

  • SearchBar의 UI는 다음과 같습니다.
  • SearchBar가 수행해야하는 기능은 서치바의 검색버튼이나 키보드의 검색버튼을 누르면 SearchBar의 text를 상위 View로 전달해주는 것입니다.

1. SearchBar.Swift

가장 먼저,
SearchBar만을 위한 SearchBar.Swift 파일을 생성해줍니다.

SearchBar.Swift 파일내에서
SearchBar 클래스를 생성합니다.

import UIKit
import RxSwift
import RXCocoa

class SearchBar: UISearchBar {

}

이때 UISearchBar는 UIKit framework에 속하는 것이기 때문에
import UIKit을 꼭 해주어야합니다.

또한 Rx로 작성할 것이므로 RxSwift와 RxCocoa도 import 해줍니다.

3. disposeBag

class SearchBar: UISearchBar {
	let disposeBag = DisposeBag()
    let searchButton = UIButton()
}
  • observable이 종료되고난 후 메모리 누수를 막기 위해
    이를 담아 해제해줄 disposeBag을 만들어 줍니다.
  • 서치바 오른쪽에 위치할 검색버튼 searchButton도 만들어 줍니다.

4. Event

class SearchBar: UISearchBar {
    let disposeBag = DisposeBag()
    let searchButton = UIButton()
   
 
    let searhButtonTapped = PublishRelay<Void>()
    var shouldLoadResult = Observable<String>.of("")
}
    
  • 검색 버튼을 눌렀을 때 발생하는 이벤트를 구독할 searchButtonTapped를 만들어줍니다.
    • 이때 SearchButtonTapped는 UI에서 발생하는 이벤트이기 때문에 PublishSubject보다 error을 방출하지 않는 PublishReplay를 사용하는 것이 더 적합합니다.
  • 검색 버튼이 눌려진 후 SearchBar의 text를 상위 뷰로 전달하는 ShouldLoadResult 이벤트 Sequence를 만들어줍니다.
    • 초기엔 아무것도 작성되어있지 않기에 빈 값 ""을 넣어줍니다.

5. bind(), attribute(), layout()

세 가지 메소드 bind(), attribute(), layout()을 만들어

// -> Reative - Bind 
private func bind() {

}
// -> UI - View attribution
private func attribute() {

}
// -> UI - SubviewLayout
private func layout() {

}

각각의 메소드에 필요한 코드를 나눠 작성해 줍니다.

override init(frame: CGRect) {
    super.init(frame: frame)
        
    bind()
    attribute()
    layout()
}
  • 세 메소드는 init에서 호출해줍니다.

6. Bind()

bind 메소드에서 searchBar 이벤트를 작성해줍니다.

- Bind SearchButtonTapped

먼저 위에서 생성해준 searchButtonTapped는

  • searchBar 오른쪽의 searchButton을 누르거나
  • 키보드의 검색버튼을 누르는 경우

발생합니다.

따라서 , 위 두 이벤트를 merge 연산자를 사용하여 합쳐줍니다.

Observable
	.merge(
    	searchButton.rx.tap.asObservable()
        self.rx.searchButtonClicked.asObservable()
    )
  • RxCocoa에서 버튼의 탭 이벤트를 지원하기때문에, searchButton.rx.tap 으로 작성할 수 있습니다.
    • 이때 tap의 타입은 ControlEvent<Void'> { get } 이기때문에 asObservable()로 타입변환해줍니다.
  • RxCocoa에서 키보드 검색버튼 탭 이벤트를 지원하기 때문에, self.rx.searchButtonClicked로 작성할 수있습니다.
    • 마찬가지로 serachButtonClicked의 타입 또한 ControlEvent<Void'> { get } 이기때문에 asObservable()로 타입변환해줍니다.
Observable
	.merge(
    	searchButton.rx.tap.asObservable()
        self.rx.searchButtonClicked.asObservable()
    )
    .bind(to: searchButtonTapped)
    .disposed(by: disposebag)
  • 합쳐진 위 두 가지 이벤트가 발생할 때마다 searchButtonTapped가 알 수 있도록 bind 해줍니다.

- SearchButtonTapped event

SearchButtonTapped가 발생하면
키보드가 내려가는 이벤트가 발생해야합니다.

따라서

SearchButtonTapped
	.asSignal()
    .emit(to: self.rx.endEditing)
    .disposed(by: disposeBag)
  • asSignal()을 사용해서 PublishRelay -> observable()로 변환해줍니다.
    • 구독 이후의 이벤트만 제공하고, 메인스트림에서 실행하며 error를 방출하지 않아 UI이벤트를 다루는데에 적합하기에 asSignal()을 사용합니다.
  • 키보드가 내려가는 이벤트를 방출하도록 emit() 연산자를 사용합니다.
    • Signal -> emit()
    • Driver -> drive()
extension Reactive where Base: Searchbar {
	var endEditing: Binder<Void> {
    	return Binder(base) { base, _ in
        	base.endEditing(true)
        }
    }
}
  • endEditing은 RxCocoa에서 지원하지 않기 때문에 extension으로 endEditing을 작성하여 endEditing을 rx스럽게 사용할 수 있습니다.

- ShouldLoadResult

SearchButtonTapped가 실행되면, 상위 뷰에 SearchBar Text를 전달하는 ShouldLoadResult가 실행되어야합니다.

SearchButtonTapped가 ShouldLoadResult의 trigger입니다.

self.shouldLoadResult = SearchButtonTapped
	.withLatestFrom(self.rx.text) { $1 ?? "" }
    .filter { !$0.isEmpty }
    .distinctUntilChanged()
  • withLatestFrom : SearchBar의 가장 최신의 text를 전달합니다.
  • filter : text가 비어있지 않은 경우만 전달합니다.
  • distinctUntilChanged() : text가 중복인 경우 전달하지 않습니다.

7. attribute()

searchBar의 UI 를 설정해줍니다.
여기서는 SearchButton 밖에 설정해 줄 것이 없기 때문에

private func attribute() {
        searchButton.setTitle("검색", for: .normal)
        searchButton.setTitleColor(.systemBlue, for: .normal)
    }

위와 같이 설정해줍니다.

8. layout()

snapKit을 사용해서 searchButton과 searchTextField 의 constraints를 설정해줍니다.

private func layout() {
	self.addSubview(searchButton)
    
    searchTextField.snp.makeConstraints {
    	$0.leading.equalToSuperview().inset(12)
        $0.trailing.equalTo(searhButton.snp.leading).offSet(-12)
        $0.centerY.equalToSuperview()
    }
    
    searchButton.snp.makeConstraints {
    	$0.centerY.equalToSuperview()
        $0.trailing.equalToSuperview.inset(12)
    }
}

0개의 댓글