개념과 함께 RxCocoa로 실시간 검색 화면을 만들어보자 !!
RxCocoa란?
RxSwift를 기반으로 만든 것이 RxCocoa인데 ..
왜 RxSwift가 있는데 RxCocoa가 만들어졌냐?!
이름에서 알 수 있는 것처럼 RxCocoa는 애플 환경의 앱을 제작하기 위한 도구들을 모아놓은 Cocoa Framework를 더 잘 사용할 수 있도록 만들어 놓은 Rx이다. (Rx+Cocoa Framework라고 생각할 수 있다.)
RxCocoa는 RxSwift를 기반으로 만들어진 라이브러리이기 때문에, 사용하기 위해서는 SPM 또는 CocoaPod과 같이 의존성 도구를 통해서 별도로 추가해야한다.
RxCocoa는 Cocoa Touch 프레임워크에 Reactive(반응형)의 장점을 더해준 라이브러리이다.
RxSwift를 기반으로 구동되며, RxSwift와 함께 설치해야 한다.
Observable Type / Observer Type
실습
만들고자하는 화면은 아래와 같다.
#View
먼저, 뷰를 만들어보자.
import UIKit
import RxSwift
import RxCocoa
import SnapKit
import Then
final class ReactiveSearchViewController: UIViewController {
// MARK: - UI Property
private var searchBar = UISearchBar()
private var tableView = UITableView(frame: .zero, style: .plain)
// MARK: - Property
private var viewModel = ReactiveSearchViewModel()
// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
configureHierarchy()
setAttribute()
bind()
configureTableView()
}
}
extension ReactiveSearchViewController: BaseViewControllerAttribute {
func configureHierarchy() {
view.addSubview(searchBar)
view.addSubview(tableView)
searchBar.snp.makeConstraints { make in
make.top.horizontalEdges.equalTo(view.safeAreaLayoutGuide)
make.height.equalTo(50)
}
tableView.snp.makeConstraints { make in
make.top.equalTo(searchBar.snp.bottom)
make.horizontalEdges.bottom.equalTo(view.safeAreaLayoutGuide)
}
}
func setAttribute() {
view.backgroundColor = .white
}
func configureTableView() {
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "SearchCell")
tableView.rowHeight = 40
}
func bind() {
}
}
사용자의 인터랙션을 포함한 이벤트 핸들링을 생각하지 않고, 뷰! 화면!만 만들면 위와 같이 코드를 작성할 수 있다.
그리고 이제 차근차근 화면을 만들어보자. (+ 그리고 인터렉션 .. 이벤트도 .. !)
초기 데이터를 넣어보자.
#ViewModel
데이터는 모델이고, 이 모델은 뷰모델에서 관리해야한다.
위와 같은 실시간 검색 화면에서 필요한 모델은 무엇이 있을까?
-> 검색을 하기 전 리스트 될 데이터 목록이 필요하다.
-> 그리고 필터를 했을 때, 걸러진 데이터 목록이 필요하다.
그렇다면, 이 뷰모델에서 사용될 비즈니스 로직은?
사용자가 입력한 키워드에 해당하는 리스트를 필터링 해주는 기능이다.
이것을 바탕으로 코드를 작성하면 뷰모델 코드는 아래와 같다.
import Foundation
import RxCocoa
import RxSwift
final class ReactiveSearchViewModel {
let items = ["iPad", "Mac", "Apple Watch", "MacBook Pro"]
var filteredItems: BehaviorRelay<[String]> = BehaviorRelay(value: ["iPad", "Mac", "Apple Watch", "MacBook Pro"])
func filterItems(with query: String) {
let filterd = query != "" ? items.filter { $0.contains(query) } : items
filteredItems.accept(filterd)
}
}
처음 보여질 데이터는 String 배열로 작성하면 되고,
검색에 따라서 반응형으로 이벤트를 방출할 데이터는 초기값이 있으면서 UI에 특화된 형태로 만들 수 있으므로 BehaviorRelay 형태로 만들 수 있다.
그리고 검색어를 쿼리로 받아서, 이에 해당하는 데이터를 걸러 accept로 이벤트를 방출하되,
만약 사용자가 아직 입력값을 넣지 않아서 ""인 경우에는 빈배열이 전달되므로 기존의 리스트를 보여주기 위해서 ""인 경우에는 items를 반환한다.
#View(Rx)
다시 뷰로 돌아와서, 이제 이벤트에 대한 처리를 해보자.
- 사용자에게서 SearchBar를 통해 이벤트를 받는 경우,
- 검색어를 받아서 (String? -> String -> 0.5초의 간격 -> 같은 값이 없도록)
- 해당 검색어를 뷰모델의 필터 함수의 인자로 전달
- 뷰모델에서 필터가 되어서 accept로 데이터에 대한 변화된 이벤트를 전달하면
- 테이블 뷰가 이 데이터를 바탕으로
- 다시 UI를 업데이트
위의 흐름을 bind 코드 안에 작성하면 아래와 같다.
func bind() {
searchBar.rx.text.orEmpty
.debounce(RxTimeInterval.microseconds(5), scheduler: MainScheduler.instance)
.distinctUntilChanged()
.withUnretained(self)
.bind { vc, value in
vc.viewModel.filterItems(with: value)
}
.disposed(by: disposeBag)
viewModel.filteredItems
.bind(to: tableView.rx.items(cellIdentifier: "SearchCell", cellType: UITableViewCell.self)) { (row, element, cell) in
cell.textLabel?.text = "\(row)번 - \(element)"
}
.disposed(by: disposeBag)
}
이렇게 하고 빌드를 해보면 아래와 같이 동작하는 것을 확인할 수 있다.
참고 블로그
Thx to 이누방구
https://inuplace.tistory.com/1201?category=1034358
'Swift' 카테고리의 다른 글
[MVVM] associatedtype으로 ViewModel 구조 추상화 (3) | 2022.11.01 |
---|---|
[MVVM] 인앤아웃버거? 인풋/아웃풋패턴! (3) | 2022.11.01 |
Method Swizzling (0) | 2022.10.13 |
[Swift] 싱글톤 패턴에서 구조체와 클래스의 차이 (0) | 2022.10.13 |
[Swift] Anti Pattern이 뭐고? (0) | 2022.10.13 |