【问题标题】:Swift - Firebase - Downloading images and data asynchronously leads to wrong display within the collection view cellSwift - Firebase - 异步下载图像和数据会导致集合视图单元格中的错误显示
【发布时间】:2021-02-10 05:45:58
【问题描述】:

我有一个集合视图,想从 firebase 异步加载图像和其他数据,并将它们显示在单元格中。但是,我当前的方法向文本数据显示错误的图像(它们根本不适合),而且,一个特定单元格中的图像会更改几次,直到它稳定下来(有时是错误的,有时是正确的)。

我的代码

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let photoCell = collectionView.dequeueReusableCell(withReuseIdentifier: "mainViewCollectionCell", for: indexPath) as! MainViewCollectionViewCell
            
    // issue when refreshing collection view after a new challenge has been created
    if (hitsSource?.hit(atIndex: indexPath.row) == nil)  {
        return photoCell
    }
    
    let challengeObject = Challenge(json: (hitsSource?.hit(atIndex: indexPath.row))!)

    let group = DispatchGroup()
    group.enter()

    // async call
    self.checkIfChallengeIsBlocked(completionHandler: { (IsUserBlocked) in
        if (IsUserBlocked) {
            return
        }
        else {
            group.leave()
        }
            
    }, challengeObject: challengeObject)
    
    group.notify(queue: .main) {
        photoCell.setChallengeLabel(title: challengeObject.title)
        // async call
        photoCell.fetchChallengeImageById(challengeObject: challengeObject)
                                  
        photoCell.checkIfToAddOrRemovePlayIcon(challengeObject: challengeObject)
        // async call   
        self.dataAccessService.fetchUserById(completionHandler: { (userObject) in
            photoCell.setFullName(userObject: userObject)
            photoCell.setStarNumber(challengeObject: challengeObject)
        }, uid: challengeObject.organizerId)
           
         // async all 
        self.dataAccessService.fetchAllParticipantsByChallengeId(completionHandler: { (participationArray) in
            photoCell.setParticipationNumber(challengeObject: challengeObject, participationArray: participationArray)
        }, challengeId: challengeObject.id)
            
        // resize image to collection view cell
        self.activityView.removeFromSuperview()
    }
    
    return photoCell
}

... 只是为了向您展示我的 MainViewCollectionViewCell

class MainViewCollectionViewCell: UICollectionViewCell  {
...
public func fetchChallengeImageById(challengeObject:Challenge) {
    self.dataAccessService.fetchChallengeImageById(completion: { (challengeImage) in
        self.challengeImageView.image = challengeImage
        self.layoutSubviews()
    }, challengeId: challengeObject.id)
} 

DataAccessService.swift

class DataAccessService {
 ...
 // fetch main challenge image by using challenge id
public func fetchChallengeImageById(completion:@escaping(UIImage)->(), challengeId:String) { 
 //throws {
    BASE_STORAGE_URL.child(challengeId).child(IMAGE_NAME).getData(maxSize: 1 * 2048 * 2048, 
 completion:({ data, error in
        if error != nil {
            print(error?.localizedDescription as Any)
            let notFoundImage = UIImage()
            completion(notFoundImage)
        } else {
            let image = UIImage(data: data!)!
            completion(image)
        }
    }))
}

...

public func fetchUserById(completionHandler:@escaping(_ user: User)->(), uid:String?) { // 
throws{
    var userObject = User()
    let _userId = UserUtil.validateUserId(userId: uid)
    USER_COLLECTION?.whereField("uid", isEqualTo: _userId).getDocuments(completion: { 
 (querySnapshot, error) in
        
        if error != nil {
            self.error = error
            print(error?.localizedDescription as Any)
        } else {
            for document in querySnapshot!.documents {
                userObject = User(snapShot: document)
                completionHandler(userObject)
            }
        }
    })
}

谁能告诉我我需要改变什么才能使文本数据适合单元格中的正确图像?

【问题讨论】:

  • 使用异步调用,您无法保证在调用异步闭包时该单元格没有被随后重用于另一行。至少,您应该调用guard let cell = tableView.cellForRow(at: indexPath) else { return },确保它不是nil(如果它是nil,那么它会滚出视图并且不再重要),然后使用@ 987654329@ 用于更新该单元格中的控件的参考。
  • @Rob 你好,Rob,感谢您的回复。 “使用该单元格引用来更新控件”是什么意思。代码会是什么样子?我面临同样的问题
  • @Rob,请。能给个建议吗?

标签: ios swift firebase asynchronous uicollectionview


【解决方案1】:

对于获取用户数据的异步调用,重复使用单元格这一事实引入了两个问题:

  1. 当重新使用某个单元格时,请确保在异步请求正在进行时不显示前一个单元格的值。要么让collectionView(_:cellForItemAt:) 重置值,或者更好的是让单元格的prepareForReuse 确保控件已重置。

  2. 在异步请求完成处理程序中,在更新之前检查单元格是否仍然可见。您可以通过调用 collectionView.cellForItem(at:) 来完成此操作。如果生成的cellnil,则该单元格不可见并且没有任何内容可更新。

因此:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let photoCell = collectionView.dequeueReusableCell(withReuseIdentifier: "mainViewCollectionCell", for: indexPath) as! MainViewCollectionViewCell

    // make sure to initialize these so if the cell has been reused, you don't see the old values

    photoCell.label.text = nil
    photoCell.imageView.image = nil

    // now in your asynchronous process completion handler, check to make sure the cell is still visible

    someAsynchronousProcess(for: indexPath.row) {
        guard let cell = collectionView.cellForItem(at: indexPath) else { return }

        // update `cell`, not `photoCell` in here
    }

    return photoCell
}

显然,如果一个异步完成处理程序启动另一个异步请求,那么您必须重复此模式。

【讨论】:

  • 天才!这有帮助。谢谢@Rob
  • @Melodias - 超级。顺便说一句,如果这回答了您的问题,您可以考虑通过单击旁边的复选标记来接受答案。见What should I do when someone answers my question?
  • 你好@Rob。最后一个问题:) 这种方法真的干净吗?在 cellForRow.. 方法中使用异步调用?是否符合 MVC 模式?
猜你喜欢
  • 2013-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-08
  • 1970-01-01
  • 2013-12-04
  • 1970-01-01
相关资源
最近更新 更多