🩵 이미지 로딩 및 재사용 문제 해결
✔️ 문제 파악
- UITableViewCell 재사용 시 이미지뷰 초기화가 정상적으로 이루어지지 않아 이미지가 덮어씌워지는 문제.
- Main Thread에서 UI 업데이트에 대한 이슈.
✔️ 원인 분석
- 비동기 이미지 로딩을 하고 있는데, 이미지 로딩이 완료되기 전(imageView에 이미지 데이터가 설정되기 전)에 셀이 재사용되는 상황
(수정전)ImageLoader.swift
final class ImageLoader {
static func loadImage(from url: String, into imageView: UIImageView, completion: (() -> Void)? = nil) {
if let imageURL = URL(string: url) {
let task = URLSession.shared.dataTask(with: imageURL) { (data, _, error) in
if let error = error {
print("Error loading image: \(error.localizedDescription)")
return
}
else if let imageData = data, let image = UIImage(data: imageData) {
DispatchQueue.main.async {
imageView.backgroundColor = .clear
imageView.image = image
completion?()
}
}
}
task.resume()
}
}
}
- ImageLoader에서 imageView를 전달받아 내부 image를 직접 업데이트 해주는 로직
- 비동기로 이미지 url을 로딩하고 그 로딩이 끝나면 imageView에 데이터를 세팅함
- 1회라면 imageView가 비어있기 때문에 괜찮지만, 2회째부터 문제가 발생하기 시작
- imageView가 세팅된 적이 있지만 초기화되지 않았기 때문에 비동기 로딩이 완료되기 전, 이미 불러와진 이미지 데이터를 세팅해서 cell을 재사용하는 것으로 생각됨
- 첫 번째 셀에 이미지를 로딩하고 있는 도중에 두 번째 셀이 이전 이미지뷰를 가져와서 이미지가 로딩되기 전에 두 번째 셀의 이미지뷰에 이미지가 설정될 수 있음. 이로 인해 셀의 이미지뷰에 예상치 못한 이미지가 표시되는 문제가 발생
- prepareForReuse에서 이미지뷰를 초기화해도 이미지 로딩이 완료되면 다시 설정되는 문제.
- 이 문제는 비동기 이미지 로딩과 셀의 재사용이 교차되면서 발생
- prepareForReuse에서 이미지뷰를 초기화하더라도, 이미지 로딩이 비동기적으로 이루어지고, 이미지 로딩이 완료되기 전에 다른 데이터로 인해 셀이 재사용되면 이미지뷰가 예상치 못한 이미지로 설정될 수 있음
✔️ 해결책
- ImageLoader를 UIImage 반환하는 로직으로 변경
(수정후)ImageLoader.swift
final class ImageLoader {
static func loadImage(from url: String, completion: @escaping (Result<UIImage?, Error>) -> Void) {
if let imageURL = URL(string: url) {
let task = URLSession.shared.dataTask(with: imageURL) { (data, _, error) in
if let error = error {
completion(.failure(error))
}
else if let imageData = data {
let image = UIImage(data: imageData)
completion(.success(image))
}
}
task.resume()
}
}
}
- UITableViewCell의 tag를 활용하여 비동기 이미지 로딩 완료 후 현재 셀의 상태를 확인.
- 이미지 로딩이 완료된 후에만 UI 업데이트가 이루어지도록 Main Thread에서 처리.
- ImageLoader에 있던 ui 관련 코드를 클라이언트쪽으로 분리하여 단일책임원칙을 지킬 수 있게 수정함
PlayerTableViewCell.swift
func loadImage(from urlString: String) {
let currentTag = self.tag
print("currentTag: \(currentTag)")
ImageLoader.loadImage(from: urlString) { result in
// 이미지 로딩이 완료된 후에 설정됨
DispatchQueue.main.async {
if self.tag == currentTag {
switch result {
case .success(let image):
self.thumbnail.backgroundColor = .clear
self.thumbnail.image = image
case .failure(let error):
print("Error decoding image: \(error)")
self.thumbnail.image = UIImage(systemName: "questionmark.video")
}
}
}
}
}
- 셀 재사용 시에는 이미지뷰의 이미지를 초기화하고, 완료된 이미지를 새로 설정.
PlayerTableViewCell.swift
override func prepareForReuse() {
super.prepareForReuse()
thumbnail.image = nil
}
https://developer.apple.com/documentation/uikit/uitableviewcell/1623223-prepareforreuse
prepareForReuse() | Apple Developer Documentation
Prepares a reusable cell for reuse by the table view’s delegate.
developer.apple.com
https://developer.apple.com/documentation/uikit/uitableview/1614878-dequeuereusablecell
dequeueReusableCell(withIdentifier:for:) | Apple Developer Documentation
Returns a reusable table-view cell object for the specified reuse identifier and adds it to the table.
developer.apple.com
'내일배움캠프 iOS 2기' 카테고리의 다른 글
[NBCAMP] 3팀-SPABUCKS (OrderListView)코드 기능 정리 (0) | 2023.12.29 |
---|---|
[NBCAMP] 앱 개발 입문 : To do list (0) | 2023.12.20 |