从服务器下载图像时,集合视图单元格中的图像未更新。滚动集合视图时,图像将更新。
表视图的每个部分都有一个集合视图。表格视图单元格具有用于集合视图的数据源。
extension OffersCell: UICollectionViewDataSource,UICollectionViewDelegate{
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return photoViewModel.photos.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath)
(cell as! PhotoCell).imageView.contentMode = .scaleAspectFill
return cell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let photo = self.photoViewModel.photos[indexPath.row]
(cell as! PhotoCell).imageView.image = UIImage(named: "dummyImage")
ImageDownloadManager.shared.downloadImage(photo, indexPath: indexPath) { (image, imageIndexPath, error) in
if let indexPathNew = imageIndexPath, indexPathNew == indexPath {
DispatchQueue.main.async {
(cell as! PhotoCell).imageView.image = image
}
}
}
}
}
请找到图像下载器类:
typealias ImageDownloadHandler = (_ image: UIImage?, _ indexPath: IndexPath?, _ error: Error?) -> Void
final class ImageDownloadManager {
private var completionHandler: ImageDownloadHandler?
lazy var imageDownloadQueue: OperationQueue = {
var queue = OperationQueue()
queue.name = "imageDownloadQueue"
queue.qualityOfService = .userInteractive
return queue
}()
let imageCache = NSCache<NSString, UIImage>()
static let shared = ImageDownloadManager()
private init () {}
func downloadImage(_ photo: Photos, indexPath: IndexPath?, handler: @escaping ImageDownloadHandler) {
self.completionHandler = handler
guard let url = photo.getImageURL() else {
return
}
if let cachedImage = imageCache.object(forKey: photo.id as NSString) {
self.completionHandler?(cachedImage, indexPath, nil)
} else {
let operation = CustomOperation(url: url, indexPath: indexPath)
if indexPath == nil {
}
operation.queuePriority = .high
operation.downloadHandler = { (image, indexPath, error) in
if let newImage = image {
self.imageCache.setObject(newImage, forKey: photo.id as NSString)
}
self.completionHandler?(image, indexPath, error)
}
imageDownloadQueue.addOperation(operation)
}
}
func cancelAll() {
imageDownloadQueue.cancelAllOperations()
}
}
答案 0 :(得分:3)
下载图像后,在主线程上执行指令(cell as! PhotoCell).imageView.image = image
。但这不会不重新显示您的collectionView单元。
同样,collectionView:willDisplayCell:forItemAtIndexPath:
通常不会被调用。 docs说
在将单元格添加到集合视图之前,集合视图会调用此方法 内容。
但是,当您在单元格中滚动时(即当它可见时),它被称为。这就是为什么在滚动单元格后显示图像的原因。
所以我的建议是:
collectionView:cellForItemAtIndexPath:
可以配置单元格
与图像。 reloadItems(at:)
。 答案 1 :(得分:1)
这取决于您如何定义类CustomOperation
,但是问题似乎出在downloadImage
的方法ImageDownloadManager
中,您在下一行中设置了self.completionHandler = handler
。请注意,ImageDownloadManager
是单例。这意味着您开始的每个操作都用新的完成替换单例对象的completionHandler
(我敢打赌,只有最后一个单元格被刷新)。解决方案包括消除属性completionHandler
并用此替换操作下载处理程序
operation.downloadHandler = { (image, indexPath, error) in
if let newImage = image {
self.imageCache.setObject(newImage, forKey: photo.id as NSString)
}
handler(image, indexPath, error)
}
请注意,它调用上下文的处理程序,而不是下载管理器的存储属性
这是一个包含所有类和结构定义的完整示例。根据需要进行调整。
typealias ImageDownloadHandler = (_ image: UIImage?, _ indexPath: IndexPath?, _ error: Error?) -> Void
enum ImageDownloadError: Error {
case badDataURL
}
class CustomOperation: Operation {
var downloadHandler: (UIImage?, IndexPath?, Error?) -> () = { _,_,_ in }
private let url: URL
private let indexPath: IndexPath?
init(url: URL, indexPath: IndexPath?) {
self.url = url
self.indexPath = indexPath
}
override func main() {
guard let imageData = try? Data(contentsOf: self.url) else {
self.downloadHandler(nil, self.indexPath, ImageDownloadError.badDataURL)
return
}
let image = UIImage(data: imageData)
self.downloadHandler(image, self.indexPath, nil)
}
}
final class ImageDownloadManager {
private var completionHandler: ImageDownloadHandler?
lazy var imageDownloadQueue: OperationQueue = {
var queue = OperationQueue()
queue.name = "imageDownloadQueue"
queue.qualityOfService = .userInteractive
return queue
}()
let imageCache = NSCache<NSString, UIImage>()
static let shared = ImageDownloadManager()
private init () {}
func downloadImage(_ photo: Photos, indexPath: IndexPath?, handler: @escaping ImageDownloadHandler) {
//self.completionHandler = handler
guard let url = photo.getImageURL() else {
return
}
if let cachedImage = imageCache.object(forKey: photo.id as NSString) {
//self.completionHandler?(cachedImage, indexPath, nil)
handler(cachedImage, indexPath, nil)
} else {
let operation = CustomOperation(url: url, indexPath: indexPath)
if indexPath == nil {
}
operation.queuePriority = .high
operation.downloadHandler = { (image, indexPath, error) in
if let newImage = image {
self.imageCache.setObject(newImage, forKey: photo.id as NSString)
}
//self.completionHandler?(image, indexPath, error)
handler(image, indexPath, error)
}
imageDownloadQueue.addOperation(operation)
}
}
func cancelAll() {
imageDownloadQueue.cancelAllOperations()
}
}
-------------------------------------------------------
struct Photos {
let id: String
let url: URL
func getImageURL() -> URL? {
return self.url
}
}
struct PhotoViewModel {
let photos: [Photos]
}
class PhotoCell: UICollectionViewCell {
@IBOutlet weak var imageView: UIImageView!
}
class ViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
private let photoViewModel: PhotoViewModel = PhotoViewModel(
photos: [
Photos(
id: "kitty1",
url: URL(
string: "https://cdn.pixabay.com/photo/2019/06/18/11/23/cat-4282110_960_720.jpg"
)!
),
Photos(
id: "kitty2",
url: URL(
string: "https://cdn.pixabay.com/photo/2019/07/23/20/08/cat-4358536_960_720.jpg"
)!
),
Photos(
id: "kitty3",
url: URL(
string: "https://cdn.pixabay.com/photo/2016/09/28/13/15/kittens-1700474_960_720.jpg"
)!
)
]
)
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
collectionView.reloadData()
}
}
extension ViewController: UICollectionViewDataSource,UICollectionViewDelegate{
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return photoViewModel.photos.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath)
(cell as! PhotoCell).imageView.contentMode = .scaleAspectFill
return cell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let photo = self.photoViewModel.photos[indexPath.row]
(cell as! PhotoCell).imageView.image = UIImage(named: "dummyImage")
ImageDownloadManager.shared.downloadImage(photo, indexPath: indexPath) { (image, imageIndexPath, error) in
if let indexPathNew = imageIndexPath, indexPathNew == indexPath {
DispatchQueue.main.async {
(cell as! PhotoCell).imageView.image = image
}
}
}
}
}
答案 2 :(得分:0)
是的,除非按照@ReinhardMänner的说明滚动收藏集视图,否则将不会显示下载的图片
相反,您可以使用适合您需求的第三方SDK来在应用程序中进行图像下载和缓存。
我建议您使用Kingfisher SDK(以迅捷方式开发)。
易于使用和集成。它做很多事情,例如异步。下载,缓存(在内存或磁盘上),设置图像时的内置过渡动画等,并且也很受欢迎
对于您的问题,如果您使用Kingfisher
SDK,则只有一行代码。
例如。
要异步加载图像,可以在cellForRowAtItem:
方法中使用以下方法。
let url = URL(string: "https://example.com/image.png")
imageView.kf.setImage(with: url)
你们所有人需要做的是...
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as! PhotoCell
cell.imageView.contentMode = .scaleAspectFill
//I'm assuming photo is URL(in string) of Photo. if 'photo' is URL type then you can pass it directly in 'setImage' method.
let photo = self.photoViewModel.photos[indexPath.row]
let imgUrl = URL(string: photo)
//It will download image asynchronously and cache it for later use. If the image is failed to downloaded due to some issue then "dummyImage" will be set in image view.
cell.imageView.kf.setImage(with: imgUrl, placeholder: UIImage(named: "dummyImage"))
return cell
}
在这里您可以删除单元格
willDisplay:
方法。