Alex Smith
Alex Smith

Reputation: 29

UI errors with downloading files

I'm using this code to download my files and display progress in a my collection view item. But if I hide the app while some items are downloading and after open app again, the progress in the item label will stop, but it won't stop downloading. How to update the user interface after the my app opens again?

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
                if self.items[indexPath.row].state == .downloading {
                    cell.title.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: 0

Views: 125

Answers (1)

duckSern1108
duckSern1108

Reputation: 1115

In your code, you are not update downloadStatus of item when download task finish or throwing error.

To fix your problem, I suggest to add 2 closure to notify when download success or error. Here is my 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() } }
    }
    var onSuccess: ((Int) -> Void)? // <- get called when download success
    var onError: ((Error?, Int) -> Void)? // <- get called when download error
    
    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:: did finish :: \(downloadQueue)")
        }
        onSuccess?(collectionId) // call when download success
         
        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)
        onError?(error, collectionId) // call when download error
        if let idx = downloadQueue[someNumb!]?.firstIndex(of: some1!) {
            downloadQueue[someNumb!]?.remove(at: idx)
            print("remove when complete:\(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
                    let item = self.items[indexPath.row]
                    switch item.downloadStatus {
                    case .inProgress:
                        cell.label.text = "\(String(format: "%.f%%", self.items[indexPath.row].progress * 100))"
                    case .completed:
                        cell.label.text = "Completed"
                    case .failed:
                        cell.label.text = "FAIL"
                    case .none:
                        break
                    }
                }
                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]
        
        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/fec85039006734629a992d7/2017/04/file_example_MP4_640_3MG.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.onError = { [weak self] error, row in
            guard let self = self else { return }
            DispatchQueue.main.async {
                // change download status to .failed when download error
                self.items[row - 1].downloadStatus = .failed
                self.reloadItem(indexPath: .init(row: row - 1, section: 0))
            }
            
        }
        downloadManager.onSuccess = { [weak self] row in
            guard let self = self else { return }
            DispatchQueue.main.async {
                // change download status to .completed when download success
                self.items[row - 1].downloadStatus = .completed
                self.reloadItem(indexPath: .init(row: row - 1, section: 0))
            }
        }
        downloadManager.onProgress = { [weak self] (row, tableId, progress) in
            guard let self = self else { return }
            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")
    }
}

Also as I debug, method func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) is not get called when app is in background.

Upvotes: 1

Related Questions