我将
UICollectionViewCompositionalLayout
与 UICollectionViewCell
类一起使用。我还有一个DownloadManager
课。我想在单击单元格后下载文件。我正在使用 didSelectItemAt
方法来开始下载。我在 progressView
类中使用 title
和 UICollectionViewCell
来显示下载开始时的进度状态。但是,如果我在下载文件的过程中滚动我的集合视图,我的单元格下载进度将跳过并显示在另一个不下载的单元格中。换句话说,progressView
显示不同单元格中的进度,但我只下载一个文件。怎么解决?
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
}
下载管理器
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
}
}
}
}
}
}
你做错了很多事情。
您不应该在
dequeueReusableCell
方法中调用 didSelectItemAt
。
您不应该在用户每次选择单元格时触发新的下载。您需要检查文件是否已下载,如果已下载,则使用本地副本。 (下载很慢!)
您不应该尝试通过下载管理器的 onProgress 方法更新单元格内容。相反,您应该修改数据模型并告诉集合视图单元格需要更新。 (当您滚动时,单元格会出现,并被重复使用来表示集合中的不同项目。因此,如果您在下载过程中滚动,则下载管理器尝试更新的单元格可以滚动到屏幕外,扔进回收站箱,然后拾取并分配给不同的项目。)这可能是您描述的问题的原因。
您不应该从
reloadData()
方法中调用 createCollectionView()
。
这些只是我一眼看到的。集合视图和表视图是复杂的野兽,并不像您想象的那样工作。我建议学习一些有关创建它们的教程。当我们第一次开始使用它们时,我们都会被单元重用错误所困扰。