Alex Smith
Alex Smith

Reputation: 29

How to show progress in collection view item?

I'm using UICollectionViewCompositionalLayout with UICollectionViewCell class. Also I have a DownloadManager class. I want to download a file after clicking a cell. I'm using didSelectItemAt method to start downloading. And I'm using progressView and title in UICollectionViewCell class to show the progress state when downloading starts. But if I scroll my collection view when my file is in the process of downloading my cell downloading progress will jump over and show up in another cell that does not downloading. In other words, the progressView shows progress in different cells, but I'm only downloading one file. How to fix it?

enum DownloadStatus {
    case none
    case inProgress
    case completed
    case failed
}

struct item {
    var number: Int!
    var downloadStatus: DownloadStatus = .none
    init(number: Int) { self.number = number }
}

var downloadQueue = [Int: [Int]]()
var masterIndex = 0

extension URLSession {
    func getSessionDescription () -> Int { return Int(self.sessionDescription!)! } // Item ID
    func getDebugDescription () -> Int { return Int(self.debugDescription)! }      // Collection ID
}

DownloadManager

class DownloadManager : NSObject, URLSessionDelegate, URLSessionDownloadDelegate {
    
    static var shared = DownloadManager()
    var identifier : Int = -1
    var collectionId : Int = -1
    var folderPath : String = ""
    typealias ProgressHandler = (Int, Int, Float) -> ()
    
    var onProgress : ProgressHandler? {
        didSet { if onProgress != nil { let _ = activate() } }
    }
    
    override init() {
        super.init()
    }
    
    func activate() -> URLSession {
        let config = URLSessionConfiguration.background(withIdentifier: "\(Bundle.main.bundleIdentifier!).background.\(NSUUID.init())")
        let urlSession = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
        urlSession.sessionDescription = String(identifier)
        urlSession.accessibilityHint = String(collectionId)
        return urlSession
    }
    
    private func calculateProgress(session : URLSession, completionHandler : @escaping (Int, Int, Float) -> ()) {
        session.getTasksWithCompletionHandler { (tasks, uploads, downloads) in
            let progress = downloads.map({ (task) -> Float in
                if task.countOfBytesExpectedToReceive > 0 {
                    return Float(task.countOfBytesReceived) / Float(task.countOfBytesExpectedToReceive)
                } else {
                    return 0.0
                }
            })
            completionHandler(session.getSessionDescription(), Int(session.accessibilityHint!)!, progress.reduce(0.0, +))
        }
    }
    
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL){
        
        let stringNumb = (session.accessibilityHint ?? "hit")
        let someNumb = Int(stringNumb as String)
        
        let string1 = (session.sessionDescription ?? "hit")
        let some1 = Int(string1 as String)
        
        if let idx = downloadQueue[someNumb!]?.index(of: some1!) {
            downloadQueue[someNumb!]?.remove(at: idx)
            print("remove:\(downloadQueue)")
        }
        
        let fileName = downloadTask.originalRequest?.url?.lastPathComponent
        let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
        let documentDirectoryPath:String = path[0]
        let fileManager = FileManager()
        var destinationURLForFile = URL(fileURLWithPath: documentDirectoryPath.appending("/\(folderPath)"))
        do {
            try fileManager.createDirectory(at: destinationURLForFile, withIntermediateDirectories: true, attributes: nil)
            destinationURLForFile.appendPathComponent(String(describing: fileName!))
            try fileManager.moveItem(at: location, to: destinationURLForFile)
        } catch (let error) {
            print(error)
        }
        
    }
    
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        if totalBytesExpectedToWrite > 0 {
            if let onProgress = onProgress {
                calculateProgress(session: session, completionHandler: onProgress)
            }
        }
    }
    
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
                
        let stringNumb = (session.accessibilityHint ?? "hit")
        let someNumb = Int(stringNumb as String)
        
        let string1 = (session.sessionDescription ?? "hit")
        let some1 = Int(string1 as String)
        
        if let idx = downloadQueue[someNumb!]?.index(of: some1!) {
            downloadQueue[someNumb!]?.remove(at: idx)
            print("remove:\(downloadQueue)")
        }
    }
    
}

UICollectionView

class CollectionController: UIViewController, UICollectionViewDelegate {
    
    typealias ProgressHandler = (Int, Float) -> ()
    var onProgress : ProgressHandler?
    var items = [item]()
        
    var collectionView: UICollectionView!
    var dataSource: UICollectionViewDiffableDataSource<Section, Item>?
    let sections = Bundle.main.decode([Section].self, from: "carouselSection.json")
    
    override func viewDidLoad() {
        super.viewDidLoad()
        createCollectionView()
        setupScrollView()
        
        let count = dataSource!.snapshot().numberOfItems
        for index in 0...count {
            items.append(item(number: index))
        }
    }
    
    func setupScrollView() {
        collectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at: .centeredHorizontally, animated: false)
    }
        
    func createDataSource() {
        dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { collectionView, indexPath, item in
            switch self.sections[indexPath.section].identifier {
            case "carouselCell": return self.configure(CarouselCell.self, with: item, for: indexPath)
            default: return self.configure(CarouselCell.self, with: item, for: indexPath)
            }
        }
    }
    
    func configure<T: SelfConfiguringCell>(_ cellType: T.Type, with item: Item, for indexPath: IndexPath) -> T {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellType.reuseIdentifier, for: indexPath) as? T else { fatalError(" — \(cellType)") }
        cell.configure(with: item)
        return cell
    }
    
    func reloadData() {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections(sections)
        for section in sections { snapshot.appendItems(section.item, toSection: section) }
        dataSource?.apply(snapshot)
    }
        
    func createCollectionView() {
        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createCompositionalLayout())
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        collectionView.isScrollEnabled = false
        collectionView.delegate = self
        collectionView.contentInsetAdjustmentBehavior = .never
        view.addSubview(collectionView)
        collectionView.register(CarouselCell.self, forCellWithReuseIdentifier: CarouselCell.reuseIdentifier)
        
        createDataSource()
        reloadData()
    }
    
    func createCompositionalLayout() -> UICollectionViewLayout {
        UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in

            let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
            let item = NSCollectionLayoutItem(layoutSize: itemSize)
            
            let groupWidth = (layoutEnvironment.container.contentSize.width * 1.05)/3
            let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(groupWidth), heightDimension: .absolute(groupWidth))
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
            
            let section = NSCollectionLayoutSection(group: group)
            section.contentInsets = NSDirectionalEdgeInsets(top: (layoutEnvironment.container.contentSize.height/2) - (groupWidth/2), leading: 0, bottom: 0, trailing: 0)
            section.orthogonalScrollingBehavior = .groupPagingCentered
            
            return section
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        
        let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        let directory: String = path[0]
        let fileManager = FileManager()
        let destination = URL(fileURLWithPath: directory.appendingFormat("/\(indexPath.row+1)"))
        
        var queueArray = downloadQueue[indexPath.row+1] ?? [Int]()
        queueArray.append(indexPath.row+1)
            
        downloadQueue[indexPath.row+1] = queueArray
                
        let url = URL(string: "link")!
        let downloadManager = DownloadManager()
        downloadManager.identifier = indexPath.row+1
        downloadManager.collectionId = indexPath.row+1
        downloadManager.folderPath = "\(indexPath.row+1)"
        let downloadTaskLocal = downloadManager.activate().downloadTask(with: url)
        downloadTaskLocal.resume()
            
        var cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "carouselCell", for: indexPath) as! CarouselCell
        cell = self.collectionView?.cellForItem(at: indexPath) as! CarouselCell
        
        var item = items[indexPath.row]
        
        downloadManager.onProgress = { (row, tableId, progress) in
            print("downloadManager.onProgress:\(row), \(tableId), \(String(format: "%.f%%", progress * 100))")
            DispatchQueue.main.async {
                if progress <= 1.0 {
                    cell.progressView.progress = progress
                    if progress == 1.0 {
                        item.downloadStatus = .completed
                    } else {
                        cell.title.text = "\(String(format: "%.f%%", progress * 100))"
                        item.downloadStatus = .inProgress
                    }
                }
            }
        }
        
    }
    
}

Update for new answers:

In my project I have Item to parse JSON and I use it in collection. And item from downloadManager

Item for parse JSON

public struct Item: Decodable, Hashable {
    let index: Int              
    let title: String           
    let image: String           
    let backgroundColor: String 
    let borderColor: String     
}

Section:

public struct Section: Decodable, Hashable {
    let index: Int         
    let identifier: String 
    let title: String      
    let subtitle: String   
    let item: [Item]       
}

item from DownloadManager

struct item {
    var number: Int!
    var downloadStatus: DownloadStatus = .none
    init(number: Int) { self.number = number }
}

CollectionView cell

func configure(with item: Item) {
        title.text = item.title
        textView.backgroundColor = UIColor(item.backgroundColor)
        textView.layer.borderColor = UIColor(item.borderColor).cgColor
        progressView.layer.borderColor = UIColor(item.borderColor).cgColor
        imageView.image = UIImage(named: item.image)
    }

Upvotes: 1

Views: 123

Answers (2)

duckSern1108
duckSern1108

Reputation: 1115

As @Duncan C point out problems in your code. Here is my suggestion to refactor your code: CollectionController will interact to DownloadManager then store current progress to Item, Instead of create item to keep track of download progress, you can store download progress as local varialable in Item:

struct Item: Decodable {
    let index: Int
    let title: String
    let image: String
    let backgroundColor: String
    let borderColor: String
    
    //local variable for Download task
    //need to set this value to optional for parsing JSON work properly
    var progress: CGFloat? = 0.0 // <- store progress
    var downloadStatus: DownloadStatus? = .none
}

extension Item: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(index)
    }
}

enum DownloadStatus: String, Decodable {
    case none
    case inProgress
    case completed
    case failed
}

public struct Section: Decodable, Hashable {
    let index: Int
    let identifier: String
    let title: String
    let subtitle: String
    let item: [Item]
}

extension Item: Equatable {
    static func == (lhs: Item, rhs: Item) -> Bool {
        lhs.title == rhs.title && lhs.image == rhs.image && lhs.backgroundColor == rhs.backgroundColor &&
        lhs.index == rhs.index && lhs.borderColor == rhs.borderColor
    }
}



class CollectionController: UIViewController, UICollectionViewDelegate {
    var mapFromIndexToDownloadManager: [Item: DownloadManager] = [:] // <- cache current DownloadManager
    //...
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        //get Item
        let item: Item = ...
        if item.downloadStatus == .none || item.downloadStatus == .failed { // <- check to see if their has been an download progress for IndexPath before, also can check download status, ...
            
            // create DownloadManager
            mapFromIndexToDownloadManager[indexPath.row] = downloadManager // <- cache DownloadManager
            let downloadTaskLocal = downloadManager.activate().downloadTask(with: url)
            downloadTaskLocal.resume()
            downloadManager.onProgress = { [weak self] (row, tableId, progress) in
                guard let self = self else { return }
                var currItem = self.datasource.snapshot().itemIdentifiers
                currItem[row].progress = progress
                var snapshot = Snapshot()
                snapshot.appendSections([.main])
                snapshot.appendItems(currItem)
                // apply new snapshot to reload item
                datasource.apply(snapshot)
            }
        }
    }
}

Now in your cell, just set your progressView.progress base on progress property of Item:

func configure(with item: Item) {
    title.text = item.title
    textView.backgroundColor = UIColor(item.backgroundColor)
    textView.layer.borderColor = UIColor(item.borderColor).cgColor
    progressView.layer.borderColor = UIColor(item.borderColor).cgColor
    imageView.image = UIImage(named: item.image)
    //set progress here
    if let progress = item.progress {
        cell.progressView.progress = progress
    }
}

Updated Answer: As Alex want to store download progress in seperated model from Item. Then when download progress got update, we have to manually reload items to update UI. Here is my minimal producuable example:

import UIKit


enum DownloadStatus {
    case none
    case inProgress
    case completed
    case failed
}

struct item {
    var number: Int!
    var downloadStatus: DownloadStatus = .none
    var progress: Float = 0.0
    init(number: Int) { self.number = number }
}

var downloadQueue = [Int: [Int]]()
var masterIndex = 0

extension URLSession {
    func getSessionDescription () -> Int { return Int(self.sessionDescription!)! } // Item ID
    func getDebugDescription () -> Int { return Int(self.debugDescription)! }      // Collection ID
}

class DownloadManager : NSObject, URLSessionDelegate, URLSessionDownloadDelegate {
    
    static var shared = DownloadManager()
    var identifier : Int = -1
    var collectionId : Int = -1
    var folderPath : String = ""
    typealias ProgressHandler = (Int, Int, Float) -> ()
    
    var onProgress : ProgressHandler? {
        didSet { if onProgress != nil { let _ = activate() } }
    }
    
    override init() {
        super.init()
    }
    
    func activate() -> URLSession {
        let config = URLSessionConfiguration.background(withIdentifier: "\(Bundle.main.bundleIdentifier!).background.\(NSUUID.init())")
        let urlSession = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
        urlSession.sessionDescription = String(identifier)
        urlSession.accessibilityHint = String(collectionId)
        return urlSession
    }
    
    private func calculateProgress(session : URLSession, completionHandler : @escaping (Int, Int, Float) -> ()) {
        session.getTasksWithCompletionHandler { (tasks, uploads, downloads) in
            let progress = downloads.map({ (task) -> Float in
                if task.countOfBytesExpectedToReceive > 0 {
                    return Float(task.countOfBytesReceived) / Float(task.countOfBytesExpectedToReceive)
                } else {
                    return 0.0
                }
            })
            completionHandler(session.getSessionDescription(), Int(session.accessibilityHint!)!, progress.reduce(0.0, +))
        }
    }
    
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL){
        
        let stringNumb = (session.accessibilityHint ?? "hit")
        let someNumb = Int(stringNumb as String)
        
        let string1 = (session.sessionDescription ?? "hit")
        let some1 = Int(string1 as String)
        
        if let idx = downloadQueue[someNumb!]?.firstIndex(of: some1!) {
            downloadQueue[someNumb!]?.remove(at: idx)
            print("remove:\(downloadQueue)")
        }
        
        let fileName = downloadTask.originalRequest?.url?.lastPathComponent
        let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
        let documentDirectoryPath:String = path[0]
        let fileManager = FileManager()
        var destinationURLForFile = URL(fileURLWithPath: documentDirectoryPath.appending("/\(folderPath)"))
        do {
            try fileManager.createDirectory(at: destinationURLForFile, withIntermediateDirectories: true, attributes: nil)
            destinationURLForFile.appendPathComponent(String(describing: fileName!))
            try fileManager.moveItem(at: location, to: destinationURLForFile)
        } catch (let error) {
            print(error)
        }
        
    }
    
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        if totalBytesExpectedToWrite > 0 {
            if let onProgress = onProgress {
                calculateProgress(session: session, completionHandler: onProgress)
            }
        }
    }
    
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        
        let stringNumb = (session.accessibilityHint ?? "hit")
        let someNumb = Int(stringNumb as String)
        
        let string1 = (session.sessionDescription ?? "hit")
        let some1 = Int(string1 as String)
        
        if let idx = downloadQueue[someNumb!]?.firstIndex(of: some1!) {
            downloadQueue[someNumb!]?.remove(at: idx)
            print("remove:\(downloadQueue)")
        }
    }
    
}

public struct Item: Decodable, Hashable {
    let index: Int
    let title: String
    let image: String
    let backgroundColor: String
    let borderColor: String
}

public struct Section: Decodable, Hashable {
    let index: Int
    let identifier: String
    let title: String
    let subtitle: String
    let item: [Item]
}

class CollectionController: UIViewController, UICollectionViewDelegate {
    
    typealias ProgressHandler = (Int, Float) -> ()
    var onProgress : ProgressHandler?
    var items = [item]()
    
    var collectionView: UICollectionView!
    var dataSource: UICollectionViewDiffableDataSource<Section, Item>?
    let sections: [Section] = [.init(index: 0, identifier: "carouselCell", title: "title", subtitle: "sub", item: [
        Item(index: 0, title: "Hello", image: "", backgroundColor: "", borderColor: ""),
        Item(index: 1, title: "Hello", image: "", backgroundColor: "", borderColor: ""),
        Item(index: 2, title: "Hello", image: "", backgroundColor: "", borderColor: ""),
        Item(index: 3, title: "Hello", image: "", backgroundColor: "", borderColor: ""),
        Item(index: 4, title: "Hello", image: "", backgroundColor: "", borderColor: ""),
        Item(index: 5, title: "Hello", image: "", backgroundColor: "", borderColor: ""),
        Item(index: 6, title: "Hello", image: "", backgroundColor: "", borderColor: ""),
        Item(index: 7, title: "Hello", image: "", backgroundColor: "", borderColor: ""),
        Item(index: 8, title: "Hello", image: "", backgroundColor: "", borderColor: ""),
        Item(index: 9, title: "Hello", image: "", backgroundColor: "", borderColor: ""),
        Item(index: 10, title: "Hello", image: "", backgroundColor: "", borderColor: ""),
        Item(index: 11, title: "Hello", image: "", backgroundColor: "", borderColor: ""),
        Item(index: 12, title: "Hello", image: "", backgroundColor: "", borderColor: "")
    ])]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        createCollectionView()
        setupScrollView()
        
        let count = dataSource!.snapshot().numberOfItems
        for index in 0...count {
            items.append(item(number: index))
        }
    }
    
    func setupScrollView() {
        collectionView.collectionViewLayout = createCompositionalLayout()
        collectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at: .centeredHorizontally, animated: false)
    }
    
    func createDataSource() {
        dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { [weak self] collectionView, indexPath, item in
            guard let self = self else { return UICollectionViewCell() }
            switch self.sections[indexPath.section].identifier {
            case "carouselCell":
                let cell = self.configure(CarouselCell.self, with: item, for: indexPath)
                if indexPath.row < self.items.count { // <- update UI base on self.items
                    cell.label.text = "\(String(format: "%.f%%", self.items[indexPath.row].progress * 100))"
                }
                return cell
            default:
                return self.configure(CarouselCell.self, with: item, for: indexPath)
            }
        }
    }
    
    func configure<T: SelfConfiguringCell>(_ cellType: T.Type, with item: Item, for indexPath: IndexPath) -> T {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellType.reuseIdentifier, for: indexPath) as? T else { fatalError(" — \(cellType)") }
        cell.configure(with: item)
        return cell
    }
    
    func reloadData() {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections(sections)
        for section in sections { snapshot.appendItems(section.item, toSection: section) }
        dataSource?.apply(snapshot)
    }
    
    func createCollectionView() {
        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createCompositionalLayout())
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        collectionView.delegate = self
        collectionView.contentInsetAdjustmentBehavior = .never
        view.addSubview(collectionView)
        collectionView.register(CarouselCell.self, forCellWithReuseIdentifier: CarouselCell.reuseIdentifier)
        
        createDataSource()
        reloadData()
    }
    
    func createCompositionalLayout() -> UICollectionViewLayout {
        UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
            
            let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
            let item = NSCollectionLayoutItem(layoutSize: itemSize)
            
            let groupWidth = (layoutEnvironment.container.contentSize.width * 1.05)/3
            let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(groupWidth), heightDimension: .absolute(groupWidth))
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
            
            let section = NSCollectionLayoutSection(group: group)
            section.contentInsets = NSDirectionalEdgeInsets(top: (layoutEnvironment.container.contentSize.height/2) - (groupWidth/2), leading: 0, bottom: 0, trailing: 0)
            section.interGroupSpacing = 20
            section.orthogonalScrollingBehavior = .groupPagingCentered
            
            return section
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        
        let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        let directory: String = path[0]
        let fileManager = FileManager()
        let destination = URL(fileURLWithPath: directory.appendingFormat("/\(indexPath.row+1)"))
        
        var queueArray = downloadQueue[indexPath.row+1] ?? [Int]()
        queueArray.append(indexPath.row+1)
        
        downloadQueue[indexPath.row+1] = queueArray
        
        let url = URL(string: "https://file-examples.com/storage/fe91352fe66730de9982024/2017/04/file_example_MP4_480_1_5MG.mp4")!
        let downloadManager = DownloadManager()
        downloadManager.identifier = indexPath.row+1
        downloadManager.collectionId = indexPath.row+1
        downloadManager.folderPath = "\(indexPath.row+1)"
        let downloadTaskLocal = downloadManager.activate().downloadTask(with: url)
        downloadTaskLocal.resume()
        
        //var item = items[indexPath.row] <- only update local value
        
        downloadManager.onProgress = { (row, tableId, progress) in
            let row = row - 1
            //print("downloadManager.onProgress:\(row), \(tableId), \(String(format: "%.f%%", progress * 100))")
            DispatchQueue.main.async {
                if progress <= 1.0 {
                    self.items[row].progress = progress
                    if progress == 1.0 {
                        self.items[row].downloadStatus = .completed
                    } else {
                        //cell.title.text = "\(String(format: "%.f%%", progress * 100))"
                        self.items[row].downloadStatus = .inProgress
                    }
                    self.reloadItem(indexPath: .init(row: row, section: 0))
                }
            }
        }
        
    }
    
    func reloadItem(indexPath: IndexPath) {
        guard let needReloadItem = dataSource!.itemIdentifier(for: indexPath) else { return }
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections(sections)
        for section in sections { snapshot.appendItems(section.item, toSection: section) }
        dataSource?.apply(snapshot)
        snapshot.reloadItems([needReloadItem]) // <- reload items
        dataSource?.apply(snapshot, animatingDifferences: false)
    }
    
}


protocol SelfConfiguringCell: UICollectionViewCell {
    static var reuseIdentifier: String { get }
    func configure(with: Item)
}

class CarouselCell: UICollectionViewCell, SelfConfiguringCell {
    static var reuseIdentifier: String { "carouselCell" }
    
    let label = UILabel()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        contentView.addSubview(label)
        contentView.backgroundColor = .green
        label.translatesAutoresizingMaskIntoConstraints = false
        label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
        label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        
    }
    
    func configure(with item: Item) {
    }
    
    required init?(coder: NSCoder) {
        fatalError("zzzzz")
    }
}

Upvotes: 1

Duncan C
Duncan C

Reputation: 131481

You are doing a number of things incorrectly.

  • You should not be calling dequeueReusableCell in your didSelectItemAt method.

  • You should not be triggering a new download every time the user selects a cell. You need to check to see if the file has already been downloaded and use the local copy if so. (Downloading is slow!)

  • You should not be trying to update cell contents from your download manager's onProgress method. Instead, you should modify your data model and tell the collection view that the cell needs updating. (Cells get when you scroll, and get reused to represent a different item in your collection. So if you scroll while a download is in progress, the cell the download manager is trying to update can be scrolled off-screen, tossed into the recycle bin, and then picked up and assigned to a different item.) This is likely the cause of the problem you describe.

  • You should not be calling reloadData() from your createCollectionView() method.

Those are just the things I saw at a glance. Collection views and table views are complex beasts and don't work the way you might think they do. I suggest studying some tutorials on creating them. We all get bitten by cell reuse bugs when we first start out with them.

Upvotes: 1

Related Questions