| 시연 영상 |
|---|
![]() |
viewDidLoad -> fetchHomeSectionsData() -> sections 저장 -> reloadData()diffable datasource로 바꾸려면 Hashable, snapshot 구성, supplementaryView 처리 방식까지 같이 정리해야 해서 복잡도는 올라감.diffable datasource로 가는 게 더 의미 있다고 생각되긴 함HomeView 안에서 visibleItemsInvalidationHandler로 페이지를 계산하고, 그걸 changeToCurrentPage 클로저로 HomeViewController에 전달해서 해당 footer의 pageControl.currentPage를 바꾸고 있음.VC가 pageControl의 UI를 갱신하는 구조.검색어 입력
-> 잠깐 멈출 때까지 기다림
-> 같은 검색어면 무시
-> 최신 검색어만 사용
-> 비어 있으면 빈 결과
-> 아니면 로딩 시작
-> API 요청
-> 성공하면 sections 방출 + 로딩 종료
-> 실패하면 에러 메시지 방출 + 로딩 종료 + 빈 결과 방출
-> UI가 쓰기 좋은 Output으로 반환
func transform(input: Input) -> Output {
let loadingRelay = BehaviorRelay<Bool>(value: false)
let errorRelay = PublishRelay<String>()
let sections = input.queryText
.debounce(.milliseconds(400), scheduler: MainScheduler.instance)
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.distinctUntilChanged()
.flatMapLatest { [weak self] query -> Observable<[SearchSectionModel]> in
guard let self else { return .just([]) }
if query.isEmpty {
return .just([])
}
loadingRelay.accept(true)
return self.fetchSearchData(query: query)
.asObservable()
.do(onNext: { _ in
loadingRelay.accept(false)
})
.catch { error in
loadingRelay.accept(false)
let message = self.makeErrorMessage(from: error)
errorRelay.accept(message)
return .just([])
}
}
.asDriver(onErrorJustReturn: [])
[]가 나오는 경우는 최소 3개:
.just([]).just([])그냥 다 빈값이라고 한 번에 처리 해버리면 어색할 수 있음
struct Output {
let sections: Driver<[SearchSectionModel]>
let isLoading: Driver<Bool>
let errorMessage: Signal<String>
let isEmpty: Driver<Bool>
}
func transform(input: Input) -> Output {
let loadingRelay = BehaviorRelay<Bool>(value: false)
let errorRelay = PublishRelay<String>()
let sections = input.queryText
.debounce(.milliseconds(400), scheduler: MainScheduler.instance)
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.distinctUntilChanged()
.flatMapLatest { [weak self] query -> Observable<[SearchSectionModel]> in
guard let self else { return .empty() }
if query.isEmpty {
return .just([])
}
loadingRelay.accept(true)
return self.fetchSearchData(query: query)
.asObservable()
.do(onNext: { _ in
loadingRelay.accept(false)
})
}
.asDriver(onErrorRecover: { error in
loadingRelay.accept(false)
let message = self.makeErrorMessage(from: error)
errorRelay.accept(message)
return .empty()
})
let isEmpty = sections
.map { $0.isEmpty }
.distinctUntilChanged()
return Output(
sections: sections,
isLoading: loadingRelay.asDriver(),
errorMessage: errorRelay.asSignal(),
isEmpty: isEmpty
)
}
asDriver(onErrorRecover:): asDriver 메서드 변경해서 오류처리 할 수 있도록 변경.empty() 변경해서 상황을 더 명확하게 나눔Output에 let isEmpty: Driver<Bool> 추가해서 sections가 비었을 때 어떤 UI를 보여줄건지 결정할 수 있도록 함