Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- ios
- RxCocoa
- clean architecture
- swift documentation
- Human interface guide
- 리팩토링
- Xcode
- 스위프트
- 애니메이션
- Clean Code
- collectionview
- Refactoring
- 리펙토링
- combine
- map
- MVVM
- 리펙터링
- uitableview
- UITextView
- Observable
- SWIFT
- rxswift
- 클린 코드
- ribs
- Protocol
- UICollectionView
- swiftUI
- tableView
- uiscrollview
- HIG
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] RxDataSources를 사용한 PrefetchItems, Pagination (페이지네이션) 본문
iOS 응용 (swift)
[iOS - swift] RxDataSources를 사용한 PrefetchItems, Pagination (페이지네이션)
jake-kim 2021. 12. 19. 19:08
사용한 기초 프레임워크 참고
- ReactorKit
- RxCocoa
- Moya/RxSwift
- Kingfisher
사용 API
- Unsplash API
- page별로, 랜덤 이미지를 로드하는 API
PrefetchItems
- tableView, collectionVIew와 같이 ScrollView의 스크롤할때 아직 화면에서 보이지 않지만 그 다음 보여야하는 cell에 관한 정보를 미리 얻어오는 것
- 정보를 미리 얻어와서, 불러와야할 이미지 url을 알고 스크롤 하기전에 prefetchItems 이벤트가 발생할때 미리 로딩하는 것
Pagination
- API 호출 시 page정보를 가지고 있어서, 정보를 한꺼번에 가져오지 않고 page=1, page=2, page=3와 같이 page별로 쪼개서 API호출하고 이미지를 업데이트 시키는 과정
- 전체 content의 높이(contentSize.height), 현재 스크롤된 위치(contentOffset.y) 값을 가지고, 본래의 사이즈보다 작은 경우 데이터를 맨 아래로 내리기 전에 호출
- 구체적인 개념은 이곳 참고
PrefetchItems 구현 방법
- ViewController에서는 prefetchItems 이벤트를 받는 처리 추가
- collectionView.rx.prefetchItems 사용
- items 정보 획득 가능
// PhotoViewController
self.photoCollectionView.rx.prefetchItems
.throttle(.seconds(1), scheduler: MainScheduler.asyncInstance)
.observe(on: MainScheduler.asyncInstance)
.asObservable()
.map(dataSource.items(at:))
.map(Reactor.Action.prefetchItems)
.bind(to: reactor.action)
.disposed(by: disposeBag)
- reactor쪽에서는 prefetchItems을 받아서, Kingfisher를 통해 해당 url에 관한 image를 미리 캐싱해놓는 작업 진행
- 캐싱은 ImagePrefetcher(resource:).start()로 사용
- 캐싱은 ImagePrefetcher(resource:).start()로 사용
// PhotoViewReactor.swift
func mutate(action: Action) -> Observable<Mutation> {
switch action {
...
case .prefetchItems(let items):
var urls = [URL]()
items.forEach {
if case let .main(photo) = $0,
let url = URL(string: photo.urlString) {
urls.append(url)
}
}
ImagePrefetcher(resources: urls).start() // <- 캐싱
return .empty()
}
Pagination 구현 방법
- pagination을 구현하기 전에 UIImageView에 Kingfisher를 통해 캐싱하는 코드를 extension으로 추가
import UIKit import Kingfisher extension UIImageView { func setImage(with urlString: String) { ImageCache.default.retrieveImage(forKey: urlString, options: nil) { result in switch result { case .success(let value): if let image = value.image { //캐시가 존재하는 경우 self.image = image } else { //캐시가 존재하지 않는 경우 guard let url = URL(string: urlString) else { return } let resource = ImageResource(downloadURL: url, cacheKey: urlString) self.kf.indicatorType = .activity self.kf.setImage( with: resource, options: [ .transition(.fade(1.2)), .forceTransition ] ) } case .failure(let error): print(error) } } } }
- Pagination을 처리하기 위해 PhotoViewController에서 scroll될때마다 pagination처리에 필요한 값들을 reactor에게 넘기도록 코드 추가
// PhotoViewController.swift self.photoCollectionView.rx.didScroll .withLatestFrom(self.photoCollectionView.rx.contentOffset) .map { [weak self] in Reactor.Action.pagination( contentHeight: self?.photoCollectionView.contentSize.height ?? 0, contentOffsetY: $0.y, scrollViewHeight: UIScreen.main.bounds.height ) } .bind(to: reactor.action) .disposed(by: disposeBag)
- Reactor에서 해당 값을 받아서 pagination을 진행할지 확인
- pagination 진행할지 확인하는 개념은 이곳 참고
(아래 코드에서는 mutate(action:) 함수, .pagination 케이스에서 진행
// PhotoViewReactor.swift class PhotoViewReactor: Reactor { enum Action { ... case pagination( contentHeight: CGFloat, contentOffsetY: CGFloat, scrollViewHeight: CGFloat ) ... func mutate(action: Action) -> Observable<Mutation> { switch action { ... case let .pagination(contentHeight, contentOffsetY, scrollViewHeight): let paddingSpace = contentHeight - contentOffsetY if paddingSpace < scrollViewHeight { return getPhotos() } else { return .empty() } } private func getPhotos() -> Observable<Mutation> { self.currentPage += 1 let photoRequest = PhotoRequest(page: currentPage) return self.provider.photoService.getPhotos(photoRequest: photoRequest) .map { (photos: [Photo]) -> [PhotoSection.Item] in let photoSectionItem = photos.map(PhotoSection.Item.main) return photoSectionItem } .map(Mutation.updateDataSource) } func reduce(state: State, mutation: Mutation) -> State { var state = state switch mutation { case .updateDataSource(let sectionItem): state.photoSection.items.append(contentsOf: sectionItem) } return state } }
- pagination 진행할지 확인하는 개념은 이곳 참고
* 모든 소스 코드: https://github.com/JK0369/ExPaginationWithRxDataSources
'iOS 응용 (swift)' 카테고리의 다른 글
[iOS - swift] RxSwift로 Timer 구현 방법 (Foreground, Background, interval 연산자) (0) | 2021.12.22 |
---|---|
[iOS - swift] RxSwift, RxSwiftExt, RxOptional 많이 쓰는 연산자 정리 (0) | 2021.12.21 |
[iOS - swift] Static Library vs Dynamic Library (Cocoapods의 use_frameworks! 의미, 정적 라이브러리, 동적 라이브러리) (0) | 2021.12.14 |
[iOS - swift] Operation, OperationQueue, 동시성 (4) | 2021.12.12 |
[iOS - swift] KeyPath, map(\.) 연산자 (0) | 2021.12.11 |
Comments