무한 스크롤을 구현 중 스크롤 할 때마다 버벅거리고 중복된 이미지가 보임
//MainViewModel
final class MainViewModel {
private let disposeBag = DisposeBag()
let pokemonListSubject = BehaviorSubject<[Pokemon]>(value: [])
let pokemonDetailSubject = BehaviorSubject<PokemonDetail?>(value: nil)
let pokemonImageSubject = BehaviorSubject<UIImage?>(value: nil)
private var currentPage = 0
private let limit = 20
func fetchPokemonData() {
// 페이지를 증가시키면서 데이터 가져오기
let offset = currentPage * limit
guard let url = APIEndpoint.pokemonListURL(limit: limit, offset: offset) else {
pokemonListSubject.onError(NetworkError.invalidUrl)
return
}
NetworkManager.shared.fetch(url: url)
.subscribe(onSuccess: { [weak self] (pokemonResponse: PokemonResponse) in
// 기존 데이터에 새로운 데이터를 추가
self?.currentPage += 1
var currentPokemonList = try! self?.pokemonListSubject.value() ?? []
currentPokemonList.append(contentsOf: pokemonResponse.results)
self?.pokemonListSubject.onNext(currentPokemonList)
}, onFailure: { [weak self] error in
self?.pokemonListSubject.onError(error)
}).disposed(by: disposeBag)
}
}
final class MainViewModel {
private let disposeBag = DisposeBag()
let pokemonListSubject = BehaviorSubject<[Pokemon]>(value: [])
let pokemonDetailSubject = BehaviorSubject<PokemonDetail?>(value: nil)
let pokemonImageSubject = BehaviorSubject<UIImage?>(value: nil)
private var currentPage = 0
private let limit = 20
private var isLoading = false // 로딩 중 상태 추가
func fetchPokemonData() {
guard !isLoading else { return } // 이미 로딩 중이면 요청하지 않음
isLoading = true
let offset = currentPage * limit
guard let url = APIEndpoint.pokemonListURL(limit: limit, offset: offset) else {
pokemonListSubject.onError(NetworkError.invalidUrl)
isLoading = false
return
}
NetworkManager.shared.fetch(url: url)
.subscribe(onSuccess: { [weak self] (pokemonResponse: PokemonResponse) in
guard let self = self else { return }
do {
// 새로운 데이터가 로드되면 기존 데이터에 추가
self.currentPage += 1
var currentPokemonList = try self.pokemonListSubject.value()
// 중복된 데이터를 추가하지 않도록 필터링
let newPokemons = pokemonResponse.results.filter { newPokemon in
!currentPokemonList.contains { $0.url == newPokemon.url }
}
currentPokemonList.append(contentsOf: newPokemons)
self.pokemonListSubject.onNext(currentPokemonList)
self.isLoading = false
} catch {
// 오류 처리
print("Error fetching Pokémon list: \(error)")
self.isLoading = false
}
}, onFailure: { [weak self] error in
self?.pokemonListSubject.onError(error)
self?.isLoading = false
}).disposed(by: disposeBag)
}
스크롤을 내렸을 때 중복 데이터가 보이는 현상은 없어진 것을 알 수 있다. 하지만 아직 스크롤을 다시 올렸을 때 데이터가 돌아오는 속도가 너무 느린 것 같다.
UICollectionView에서 cell이 재사용될 때, 이전에 비동기적으로 로드된 이미지나 데이터를 덮어쓰게 되는 경우인 것 같다는 생각을 함
final class MainCollectionViewCell: UICollectionViewCell {
static let identifier = "MainCollectionViewCell"
private let viewModel = MainViewModel()
private var disposeBag = DisposeBag()
override func prepareForReuse() {
super.prepareForReuse()
self.imageView.image = nil
self.disposeBag = DisposeBag() // 셀이 재사용되면 새로운 disposeBag을 할당
self.currentIndexPath = nil // indexPath 초기화
}
// 셀에 데이터 설정
func configure(with pokemon: Pokemon) {
viewModel.fetchPokemonDetail(for: pokemon.url)
viewModel.pokemonDetailSubject
.observe(on: MainScheduler.instance)
.subscribe(onNext: { [weak self] pokemonDetail in
guard let detail = pokemonDetail else { return }
self?.viewModel.fetchPokemonImage(for: detail.id)
self?.viewModel.pokemonImageSubject
.observe(on: MainScheduler.instance)
.subscribe(onNext: { image in
self?.imageView.image = image
}, onError: { error in
print("Error fetching image: \(error)")
})
.disposed(by: self?.disposeBag ?? DisposeBag()) //
}, onError: { error in
print("Error fetching Pokémon detail: \(error)")
})
.disposed(by: disposeBag)
}
}
//수정한 코드
final class MainCollectionViewCell: UICollectionViewCell {
static let identifier = "MainCollectionViewCell"
private let viewModel = MainViewModel()
private var disposeBag = DisposeBag()
private var currentIndexPath: IndexPath? // 현재 셀의 indexPath를 추적
override func prepareForReuse() {
super.prepareForReuse()
self.imageView.image = nil
self.disposeBag = DisposeBag() // 셀이 재사용되면 새로운 disposeBag을 할당
self.currentIndexPath = nil // indexPath 초기화
}
// 셀에 데이터 설정
func configure(with pokemon: Pokemon, indexPath: IndexPath) {
// 현재 셀의 indexPath를 저장
self.currentIndexPath = indexPath
viewModel.fetchPokemonDetail(for: pokemon.url)
viewModel.pokemonDetailSubject
.observe(on: MainScheduler.instance)
.subscribe(onNext: { [weak self] pokemonDetail in
guard let detail = pokemonDetail else { return }
// 현재 셀의 indexPath와 일치하는지 확인
if self?.currentIndexPath == indexPath {
self?.viewModel.fetchPokemonImage(for: detail.id)
self?.viewModel.pokemonImageSubject
.observe(on: MainScheduler.instance)
.subscribe(onNext: { image in
self?.imageView.image = image
}, onError: { error in
print("Error fetching image: \(error)")
})
.disposed(by: self?.disposeBag ?? DisposeBag()) //
}
}, onError: { error in
print("Error fetching Pokémon detail: \(error)")
})
.disposed(by: disposeBag)
}
}
여전히 이미지가 돌아오는 속도가 느리고 셀에 잘못된 이미지가 들어가 있다...
self.currentIndexPath = indexPath
// 포켓몬 상세 정보를 가져옴
viewModel.fetchPokemonDetail(for: pokemon.url)
// 포켓몬 상세 정보 구독
viewModel.pokemonDetailSubject
.observe(on: MainScheduler.instance)
.subscribe(onNext: { [weak self] pokemonDetail in
guard let detail = pokemonDetail else { return }
// 현재 셀이 여전히 올바른 indexPath인지 확인
guard self?.currentIndexPath == indexPath else { return }
guard (self?.currentIndexPath?[1])! + 1 == detail.id else { return }
// 포켓몬 이미지를 가져옴
self?.viewModel.fetchPokemonImage(for: detail.id)
// 포켓몬 이미지 구독
self?.viewModel.pokemonImageSubject
.observe(on: MainScheduler.instance)
.subscribe(onNext: { image in
// 현재 셀이 여전히 올바른 indexPath인지 확인
guard self?.currentIndexPath == indexPath else { return }
self?.imageView.image = image
cell의 index와 해당 cell에서 요청할 이미지 id 값이 -1이므로 currentIndexPath + 1 == detail.id 일때만 이미지를 요청하도록 수정하였다.
이제 이미지가 잘못 들어가는 경우가 없고 돌아오는 속도도 훨씬 빨라졌다.
아직도 잘못 들어간다