์์ ์๋ Pagination์ ํด ๋ณธ ๊ฒฝํ์ด ๋ช ๋ฒ ์์์ง๋ง ๋ญ๊ฐ ์์ง ๊ฐ๋ ์ด ๋ช ํํ๊ฒ ์กํ์ง ์์๋ค๋ ์๊ฐ์ ๋ฑ๋ ๋ฑ๋ Pagination ์ค์ต์ ์งํํ๊ฒ ๋์๋ค.
๋ด๊ฐ ์ ์ํ ๊ธฐ๋ฅ์ ๋ค์๊ณผ ๊ฐ๋ค. (์ถ์ฒ ๋ด ๋จธ๋ฆฌ)
1. ํ ์คํธ ํํธ์ ํ ์คํธ๋ฅผ ํ ๊ธ์ ์ ๋ ฅํ ๋ ๋ง๋ค ๊ฒฐ๊ณผ๊ฐ ๋ก๋๋๋ค.
2. ์ ๋ ฅ ํ ์คํธ๊ฐ ๋ณ๊ฒฝ๋๋ฉด ๋ฐ์ดํฐ๋ฅผ ๋ค์ ๋ฐ์์จ๋ค.
3. UI๋ image๊ฐ ํ์ค์ 3๊ฐ์ฉ, API 1ํ ํธ์ถ ์ ๋ฐ์ดํฐ๊ฐ 20๊ฐ์ฉ ์ถ๊ฐ๋๋ค.
4. ๊ฐ์ฅ ์๋๋ก ์คํฌ๋กค ํ๋ฉด ๋ค์ ๊ฒฐ๊ณผ๋ฅผ ๋ถ๋ฌ์จ๋ค.
5. ์ด๋ฏธ์ง๋ ์บ์ฑ์ฒ๋ฆฌ๋์ด ์ดํ ํ์ํ ๊ฒฝ์ฐ ์บ์ฑ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๋ค.
์ฌ์ฉํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ Moya๋ก Api ํธ์ถ์ ํ๊ณ , Kingfisher๋ก ์ด๋ฏธ์ง ๋ก๋์ ์บ์ฑ์ ์ฒ๋ฆฌํ๋ค.
์ค์ต์๋ Kakao Search Image API๋ฅผ ์ฌ์ฉํ๋ค.
https://developers.kakao.com/docs/latest/ko/daum-search/dev-guide#search-image
๊ฒฐ๊ณผํ๋ฉด์ ๋ค์๊ณผ ๊ฐ๋ค.
UI๋ ์ต์ํ์ผ๋ก ๊ตฌ์ฑํ์ฌ ๊ทธ๋ค์ง ์ ๊ฒฝ์ฐ์ง ์์๋ค.. ๊ป๊ป
์๊ตฌ์ฌํญ์ ํ๋ํ๋ ์ดํด๋ณด์
1. ํ ์คํธ ํํธ์ ํ ์คํธ๋ฅผ ํ ๊ธ์ ์ ๋ ฅํ ๋ ๋ง๋ค ๊ฒฐ๊ณผ๊ฐ ๋ก๋๋๋ค.
2. ์ ๋ ฅ ํ ์คํธ๊ฐ ๋ณ๊ฒฝ๋๋ฉด ๋ฐ์ดํฐ๋ฅผ ๋ค์ ๋ฐ์์จ๋ค.
homeView.searchBar.textfield.rx.text.orEmpty
.observe(on: MainScheduler.asyncInstance)
.subscribe(onNext: { [weak self] text in
guard let self else { return }
viewModel.clearPageInfo()
requestApiFirstStep(text: text)
})
.disposed(by: disposeBag)
ํ ์คํธ๊ฐ ํ๋์ฉ ์ ๋ ฅ๋ ๋๋ง๋ค api๋ฅผ ํธ์ถํ๋ฉด ๋๋ค.
2. UI๋ image๊ฐ ํ์ค์ 3๊ฐ์ฉ, API 1ํ ํธ์ถ ์ ๋ฐ์ดํฐ๊ฐ 20๊ฐ์ฉ ์ถ๊ฐ๋๋ค.
extension HomeVC: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return viewModel.searchDatas.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = homeView.collectionView.dequeueReusableCell(withReuseIdentifier: HomeCVCell.identifier, for: indexPath) as? HomeCVCell else { return UICollectionViewCell() }
let urlString = viewModel.searchDatas[indexPath.row].imageURL
DispatchQueue.main.async {
cell.imageView.setImage(urlString: urlString)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let numberOfItemsPerRow: CGFloat = 3
let spacingBetweenItems: CGFloat = 10
let totalSpacing = (numberOfItemsPerRow - 1) * spacingBetweenItems
let availableWidth = homeView.collectionView.frame.width - totalSpacing
let calculatedItemWidth = availableWidth / numberOfItemsPerRow
return CGSize(width: calculatedItemWidth,
height: calculatedItemWidth)
}
}
3๊ฐ์ฉ ํ์ค๋ก ๋ง๋๋ ์ฝ๋๋ sizeForItemAt์ ์์ฑ๋ ๋ถ๋ถ์ด๋ค.
์ปฌ๋ ์ ๋ทฐ์ ๋๋น์์ ํ ์ค์ ๋ค์ด๊ฐ๋ ์ ๊ฐ์ ๊ฐ๊ฒฉ์ ํฌ๊ธฐ๋ฅผ ๋บ๋ค. ์ด๋ numberOfItemsPerRow - 1 ์ ๊ฐ์ด ๋๋ฉฐ ์ด ๊ฐ์ ์ ๊ฐ์ ๊ฐ๊ฒฉ์ ํด๋นํ๋ spacingBetweenItems์ ๊ฐ์ ๊ณฑํด์ ๋บ ํ ์ ์ฒด ์ปฌ๋ ์ ๋ทฐ ๋๋น์์ ๋ด๊ฐ ํ์ค์ ๋ฐฐ์นํ ์์ดํ ์ ๊ฐ์ ๋งํผ ๋๋๋ฉด ๋๋ค.
4. ๊ฐ์ฅ ์๋๋ก ์คํฌ๋กค ํ๋ฉด ๋ค์ ๊ฒฐ๊ณผ๋ฅผ ๋ถ๋ฌ์จ๋ค.
extension HomeVC : UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsetY = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
let height = scrollView.frame.height
let isEndPosition = offsetY > (contentHeight - height)
if isEndPosition && viewModel.isEnabledPaging {
let text = homeView.searchBar.textfield.text ?? ""
requestApiMoreStep(text: text)
}
}
}
๋จผ์ ํ ์ด๋ธ ๋ทฐ, ์ปฌ๋ ์ ๋ทฐ์๋ ๊ธฐ๋ณธ์ผ๋ก ์คํฌ๋กค๋ทฐ๊ฐ ๋ด์ฅ๋์ด ์์์ ์์์ผํ๋ค.
์ปฌ๋ ์ ๋ทฐ์ ๊ฐ์ฅ ์๋๋ก ์คํฌ๋กคํ๋ ์ํฉ์ ๊ทธ๋ฆผ์ผ๋ก ๊ทธ๋ฆฌ๋ฉด ์๋์ ๊ฐ๋ค.
๊ทธ๋ฆผ์ฒ๋ผ ํด๋ํฐ์์ ์ปฌ๋ ์ ๋ทฐ๊ฐ ์ฐจ์งํ๋ ๋์ด๋ 717์ด๋ผ๋ ๊ฐ์ด๊ณ , ๋ด๊ฐ api๋ฅผ ํธ์ถํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ฌ ๋ ๋ง๋ค ์ถ๊ฐ๋ก ๋ค์ด์ค๋ ๋ฐ์ดํฐ์ ๋ํ ์ปจํ ์ธ ๋์ด๊ฐ 876์ธ ์ํฉ์ด๋ค.
๊ทธ๋ฆผ ์ ์ ์ผ๋ก ํ์ํ ๊ฒ๊ณผ ๊ฐ์ด ํ๋ฉด์๋ ๋ณด์ด์ง ์์ง๋ง api ํธ์ถ ํ ๋๋ง๋ค 876๋งํผ์ ์ปจํ ์ธ ๋์ด๊ฐ ์ถ๊ฐ๋๋ค.
๋ด๊ฐ ์คํฌ๋กค ํ offsetY๋ 198.xx, ๋ฐ์ดํฐ๊ฐ ๊ทธ๋ ค์ง contents์ ๋์ด๋ 876.xx, ๊ทธ๋ฆฌ๊ณ ํ๋ฉด์ ๋์ด(ํ๋ฉด์ ๋ณด์ด๋ ์ปฌ๋ ์ ๋ทฐ์ ๋์ด)๋ 717์ด๋ค.
offsetY๋ ๋ด๊ฐ ์คํฌ๋กค ํ ๋ ๋ง๋ค ๋ณ๊ฒฝ๋ ๊ฒ์ด๊ณ , contentHeight๋ api๊ฐ ํธ์ถ ๋ ๋๋ง๋ค 876๋งํผ ๋ ์ถ๊ฐ๋ ๊ฒ์ด๊ณ , height๋ ๋ณํ์ง ์์ ๊ฒ์ด๋ค.
ํ๋ฉด์ ์ฒ์ ํค๋ฉด ๋ด ํ๋ฉด์๋ 876๋งํผ์ ์ปจํ ์ธ ๋ด์ฉ ์ค 717์ ๋์ด๋งํผ ๋ณด์ผ ๊ฒ์ด๊ณ , ์ด๋ offsetY๋ 0์ด๋ค. ๋ฐ๋ผ์ ๋ณด์ด์ง ์๋ 876 - 717 = 159 ๋งํผ์ ์์ญ์ ๋ด๊ฐ ์คํฌ๋กค ํ ์ ์๋ ์์ญ์ด ๋๊ณ , ๊ทธ๋ฌ๋ฏ๋ก offsetY๊ฐ 159๋ณด๋ค ์ปค์ง๊ฒ ๋๋ฉด ์ปฌ๋ ์ ๋ทฐ ๊ฐ์ฅ ์๋์ ๋๋ฌํ์์ ์ ์ ์๋ค.
๊ฐ์ฅ ์๋์ ๋๋ฌ ํ ํ ๊ฐ์ ๋ณด๋ฉด ๋ด ์์๊ณผ ๋ค๋ฅด๊ฒ api 3ํ ํธ์ถ ๋งํผ์ ์ปจํ ์ธ (876 * 3 = 2628)๊ฐ ์ถ๊ฐ๋์ด ์๋ค.
์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ๊ฐ์ฅ ์๋์ ๋๋ฌํ์ฌ api๋ฅผ ํธ์ถํ์๋ง์ isEnabledPaging๋ผ๋ ํ๋กํผํฐ๋ฅผ ๋ง๋ค์ด isEnabledPaging = false๋ก ์ค์ ํ์ฌ ์ด์ค ํธ์ถ์ ๋ฐฉ์งํ๋ค.
api ํธ์ถ ์๋ฃ ํ isEnabledPaging = true๋ก ํ๋ฉด ๋๋ค.
5. ์ด๋ฏธ์ง๋ ์บ์ฑ์ฒ๋ฆฌ๋์ด ์ดํ ํ์ํ ๊ฒฝ์ฐ ์บ์ฑ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๋ค.
extension UIImageView {
func setImage(urlString: String) {
ImageCache.default.retrieveImage(forKey: urlString) { 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 }
self.kf.setImage(with: url)
}
case .failure(let error):
print(error)
}
}
}
}
์ ๋จ๊ณ์์ Kingfisher๋ฅผ ์ฌ์ฉํ๋ค.
urlString์ ์ ๋ฌํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์ค์ ํ๋ฉด Kingfisher๊ฐ ์์์ ์ด๋ฏธ์ง ์บ์๊ฐ ์์ผ๋ฉด ์บ์๋ฐ์ดํฐ๋ฅผ ๋์ ธ์ฃผ๊ณ ์๋๋ฉด ์๋ก ์์ฑํ๋ค. ๊ตณ๊ตณ
Kakao Image Search API๋ฅผ ์ฌ์ฉํ๋ฉด์ ๋ช๋ช ์ด๋ฏธ์ง url์ด http์ฌ์ ํ๋ฉด์ ๋ํ๋์ง ์๋ ์ด์๊ฐ ์์๋ค.
Target - Info์์ App Transport Security Settings -> Allow Arbitrary Loads = True๋ก ํ๋ฉด http ์ด๋ฏธ์ง๋ ๋ก๋๊ฐ ๊ฐ๋ฅํ๋ค!
์ ์ฒด ์ฝ๋๋ ์๊ธฐ!
https://github.com/hililyy/ios-practice/tree/main/Pagination
'iOS' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[iOS/RxSwift] Merge, CombineLatest, Zip ๋น๊ตํ๊ธฐ (0) | 2024.01.20 |
---|---|
[iOS] NIB View ์์ฑ์๋ก ์ธ์คํด์ค ์์ฑํ๊ธฐ (BaseView, ํด๋ฆฐ์ฝ๋ ๋ ํ๊ฐ..) (1) | 2023.11.27 |
[iOS] Keychain(ํค์ฒด์ธ) ์์๋ณด๊ธฐ (0) | 2023.07.31 |
[iOS] UIImageView Orientation๊ณผ ์ด๋ฏธ์ง๋ฅผ ํ์ ํ๋ ๋ฐฉ๋ฒ (0) | 2023.07.29 |
[iOS] iOS ํธ์์๋ฆผ(APNS, FCM) (0) | 2023.06.29 |