I have 7 sections and 3 cels in section in my collectionView.
My code to download file when cell was clicked:
enum DownloadStatus {
case none
case inProgress
case completed
case failed
}
struct item {
var title : String!
var downloadStatus : DownloadStatus = .none
init(title: String) {
self.title = title
}
}
var downloadQ = [Int: [Int]]()
typealias ProgressHandler = (Int, Float) -> ()
var items = [[item]]()
var tableId = 0
var onProgress : ProgressHandler?
override func viewDidLoad() {
super.viewDidLoad()
items = [
[item(title: "item 2"),item(title: "item 2"),item(title: "item 2")],
[item(title: "item 2"),item(title: "item 2"),item(title: "item 2")],
[item(title: "item 2"),item(title: "item 2"),item(title: "item 2")],
[item(title: "item 2"),item(title: "item 2"),item(title: "item 2")],
[item(title: "item 2"),item(title: "item 2"),item(title: "item 2")],
[item(title: "item 2"),item(title: "item 2"),item(title: "item 2")],
[item(title: "item 2"),item(title: "item 2"),item(title: "item 2")]
]
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return media.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return media[section].count
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
var item = self.items[indexPath.section][indexPath.row]
downloadQ[indexPath.section]?.append(indexPath.row)
var theArray = downloadQ[indexPath.section] ?? [Int]()
theArray.append(indexPath.row)
downloadQ[indexPath.section] = theArray
// Create the actions
let url = URL(string: "link")!
let downloadManager = DownloadManager()
downloadManager.identifier = indexPath.row
downloadManager.tableId = indexPath.section
downloadManager.folderPath = "folder"
let downloadTaskLocal = downloadManager.activate().downloadTask(with: url)
downloadTaskLocal.resume()
var cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! MasterViewCell
cell = self.collectionView?.cellForItem(at: indexPath) as! MasterViewCell
cell.label?.isHidden = false
downloadManager.onProgress = { (row, tableId, progress) in
DispatchQueue.main.async {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
if appDelegate.masterVC == nil {
return
}
if appDelegate.masterVC.tableId != tableId {
return
}
let indexpath = IndexPath.init(row: row, section: indexPath.section)
var cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! MasterViewCell
cell = appDelegate.masterVC.collectionView.cellForItem(at: indexpath) as! MasterViewCell
cell.label?.text = "\(CGFloat(progress))%"
}
}
}
My Download manager class:
import Foundation
import UIKit
extension URLSession {
func getSessionDescription () -> Int {
// row id
return Int(self.sessionDescription!)!
}
func getDebugDescription () -> Int {
// table id
return Int(self.debugDescription)!
}
}
class DownloadManager : NSObject, URLSessionDelegate, URLSessionDownloadDelegate {
static var shared = DownloadManager()
var identifier : Int = -1
var tableId : 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(tableId)
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
}
})
//print("tbale id \(session.accessibilityHint ?? "hit")")
let stringNumb = (session.accessibilityHint ?? "hit")
let someNumb = Int(stringNumb as String) // 1357 as integer
let string1 = (session.sessionDescription ?? "hit")
let some1 = Int(string1 as String) // 1357 as integer
if let idx = downloadQ[someNumb!]?.index(of: some1!) {
downloadQ[someNumb!]?.remove(at: idx)
//print("remove:\(downloadQ)")
}
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) // 1357 as integer
let string1 = (session.sessionDescription ?? "hit")
let some1 = Int(string1 as String) // 1357 as integer
if let idx = downloadQ[someNumb!]?.index(of: some1!) {
downloadQ[someNumb!]?.remove(at: idx)
//print("remove:\(downloadQ)")
}
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) {
let stringNumb = (session.accessibilityHint ?? "hit")
let someNumb = Int(stringNumb as String) // 1357 as integer
let string1 = (session.sessionDescription ?? "hit")
let some1 = Int(string1 as String) // 1357 as integer
if let idx = downloadQ[someNumb!]?.index(of: some1!) {
downloadQ[someNumb!]?.remove(at: idx)
//print("remove:\(downloadQ)")
}
if totalBytesExpectedToWrite > 0 {
if let onProgress = onProgress {
calculateProgress(session: session, completionHandler: onProgress)
}
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
//debugPrint("Progress \(downloadTask) \(progress)")
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
let stringNumb = (session.accessibilityHint ?? "hit")
let someNumb = Int(stringNumb as String) // 1357 as integer
let string1 = (session.sessionDescription ?? "hit")
let some1 = Int(string1 as String) // 1357 as integer
if let idx = downloadQ[someNumb!]?.index(of: some1!) {
downloadQ[someNumb!]?.remove(at: idx)
//print("remove:\(downloadQ)")
}
//debugPrint("Task completed: \(task), error: \(String(describing: error))")
}
}
But I have a problem. My app crashes in this line - cell = self.collectionView?.cellForItem(at: indexPath) as! MasterViewCell
with this issue: Fatal error: Unexpectedly found nil while unwrapping an Optional value. How to fix it?
The problem you are facing is due to cell reuse. Each time you scroll in your collection or table, you usually deque the old cell. Which means whenever you are displaying new cell cellForRow
(table), or itemForRow
(collection), you'll have to have data for the cell to be able to feed it.
If you only store the data about your download process into the cell you'll lose them at the moment when prepareForReuse:
, system call, gets called, because the cell gets reset.
The other way around would be to use static cells. Because the static cells, never gets reset. But that's handy only when you use a small number of items on the screen. If you have tens or hundreds of cells, then it gets very costly on performance.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.