带标签、图像、GIF和视频的TableView在iOS、Swift中从firestore提取时挂起/卡住不正确

带标签、图像、GIF和视频的TableView在iOS、Swift中从firestore提取时挂起/卡住不正确,ios,swift,xcode,firebase,google-cloud-firestore,Ios,Swift,Xcode,Firebase,Google Cloud Firestore,我有带标签的tableview、imageView(用于图像、gif和视频缩略图)。我确信做了一些错误的事情,我不能处理它的完成处理程序,因为应用程序被挂起并被卡住了很长一段时间 我的模型是 struct PostiisCollection { var id :String? var userID: String? var leadDetails : NSDictionary? var company: NSDictionary? var content:

我有带标签的tableview、imageView(用于图像、gif和视频缩略图)。我确信做了一些错误的事情,我不能处理它的完成处理程序,因为应用程序被挂起并被卡住了很长一段时间

我的模型是

struct PostiisCollection {
    var id :String?
    var userID: String?
    var leadDetails : NSDictionary?
    var company: NSDictionary?
    var content: String?
    
    init(Doc: DocumentSnapshot) {
        self.id = Doc.documentID
        self.userID = Doc.get("userID") as? String ?? ""
        self.leadDetails = Doc.get("postiiDetails") as? NSDictionary
        self.company = Doc.get("company") as? NSDictionary
        self.content = Doc.get("content") as? String ?? ""
        
    }
}
我在我的视图控制器中写了这个

var postiisCollectionDetails = [PostiisCollection]()
override func viewDidLoad() {
    super.viewDidLoad()
    let docRef = Firestore.firestore().collection("PostiisCollection").whereField("accessType", isEqualTo: "all_access")
    docRef.getDocuments { (querysnapshot, error) in
        if let doc = querysnapshot?.documents, !doc.isEmpty {
            print("Document is present.")
            for document in querysnapshot!.documents {
                _ = document.documentID
                if let compCode = document.get("company") as? NSDictionary {
                    do {
                        let jsonData = try JSONSerialization.data(withJSONObject: compCode)
                        let companyPost: Company = try! JSONDecoder().decode(Company.self, from: jsonData)
                        if companyPost.companyCode == AuthService.instance.companyId ?? ""{
                            print(AuthService.instance.companyId ?? "")
                            if (document.get("postiiDetails") as? NSDictionary) != nil {
                                let commentItem = PostiisCollection(Doc: document)
                                self.postiisCollectionDetails.append(commentItem)   
                            }
                        }
                        
                    } catch {
                        print(error.localizedDescription)
                    }
                    DispatchQueue.main.async {
                        
                        self.tableView.isHidden = false
                        self.tableView.reloadData()
                    }
                }
            }
        }
    }
}
我需要检查图像视图的索引路径是否为图像、gif或具有不同参数的视频,我尝试了tableview委托和datasource方法

extension AllAccessPostiiVC: UITableViewDataSource, UITableViewDelegate {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        return postiisCollectionDetails.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "AllAccessCell", for: indexPath)
        let label1 = cell.viewWithTag(1) as? UILabel
        let imagePointer = cell.viewWithTag(3) as? UIImageView
        let getGif = arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "gif") as? NSArray
        let getPhoto = arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "photo") as? NSArray
        let getVideo = arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "video") as? NSArray
        
        label1?.text = "\(arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "title"))"
        if getGif != nil {
            let arrGif = getGif?.value(forKey: "gifUrl") as! [String]
            print(arrGif[0])
            
            let gifURL : String = "\(arrGif[0])"
            let imageURL = UIImage.gifImageWithURL(gifURL)
            imagePointer?.image = imageURL
            playButton?.isHidden = true
        }
        if getPhoto != nil  {
            let arrPhoto = getPhoto?.value(forKey: "photoUrl")  as! [String]
            print(arrPhoto[0])
            let storageRef = Storage.storage().reference(forURL: arrPhoto[0])
            storageRef.downloadURL(completion: { (url, error) in
                do {
                    let data = try Data(contentsOf: url!)
                    let image = UIImage(data: data as Data)
                    DispatchQueue.main.async {
                        imagePointer?.image = image
                        playButton?.isHidden = true
                    }
                } catch {
                    print(error)
                }
            })
        }
        if getVideo != nil {
            let arrVideo = getVideo?.value(forKey: "videoUrl")  as! [String]
            
            let videoURL = URL(string: arrVideo[0])
            let asset = AVAsset(url:videoURL!)
            if let videoThumbnail = asset.videoThumbnail{
                SVProgressHUD.dismiss()
                imagePointer?.image = videoThumbnail
                playButton?.isHidden = false
            }
        }
    }
}

如果我运行,应用程序将挂起在此页面,数据加载时间越来越长,在某些情况下,预览图像显示错误,无法处理其完成情况

,正如其他人在评论中提到的,您最好不要在
CellFroRowatineXpath
中执行后台加载

相反,最好添加一个新方法
fetchData()
,在这里执行所有服务器交互。 例如:

// Add instance variables for fast access to data
private var images = [UIImage]() 
private var thumbnails = [UIImage]()

private func fetchData(completion: ()->()) {
    // Load storage URLs
    var storageURLs = ...

    // Load data from firebase
    let storageRef = Storage.storage().reference(forURL: arrPhoto[0])
    storageRef.downloadURL(completion: { (url, error) in
         // Parse data and store resulting image in image array
         ...

         // Call completion handler to indicate that loading has finished
         completion()             
     })
}
现在,您可以在需要刷新数据时调用
fetchData()
,并在完成处理程序中调用
tableview.reloadData()
。当然,这必须在主线程上完成

这种方法简化了
cellforrowatinexpath
方法。 在这里,你可以说:

imagePointer?.image = ...Correct image from image array...

没有任何后台加载。

您以完全错误的方式处理数据<代码>数据(contentsOf:url!)-这是错误的。您应该更改图像并将其下载到目录。当您将某些内容转换为数据时,它会发生在内存(ram)中,在播放大型文件时,这不是一个好主意。您应该使用某种库将图像设置为imageview

第二件事
如果让videoThumbnail=asset.videoThumbnail
-这也是错误的。为什么要创建资产,然后从中获取缩略图?在API的响应中,您应该为所有视频的缩略图图像提供单独的URL,然后您可以再次使用
SDWebImage
加载该缩略图

您也可以对gif使用
SDWebImage


SDWebImage
的备选方案是。只需浏览这两个库并使用任何适合您的工具。

我建议使用下面的轻量级扩展从URL下载图像 使用NSCache

extension UIImageView {
 
    func downloadImage(urlString: String, success: ((_ image: UIImage?) -> Void)? = nil, failure: ((String) -> Void)? = nil) {
        
        let imageCache = NSCache<NSString, UIImage>()

        DispatchQueue.main.async {[weak self] in
            self?.image = nil
        }
        
        if let image = imageCache.object(forKey: urlString as NSString) {
            DispatchQueue.main.async {[weak self] in
                self?.image = image
            }
            success?(image)
        } else {
            guard let url = URL(string: urlString) else {
                print("failed to create url")
                return
            }
            
            let request = URLRequest(url: url)
            let task = URLSession.shared.dataTask(with: request) {(data, response, error) in
                
                // response received, now switch back to main queue
                DispatchQueue.main.async {[weak self] in
                    if let error = error {
                        failure?(error.localizedDescription)
                    }
                    else if let data = data, let image = UIImage(data: data) {
                        imageCache.setObject(image, forKey: url.absoluteString as NSString)
                        self?.image = image
                        success?(image)
                    } else {
                        failure?("Image not available")
                    }
                }
            }
            
            task.resume()
        }
    }
}
无需将imageView.downloadImage(urlString:path)放在mainQueue中,它在扩展中处理

在您的情况下:

let path = "https://i.stack.imgur.com/o5YNI.jpg"
let imageView = UIImageView() // your imageView, which will download image
imageView.downloadImage(urlString: path)
您可以在cellForRowAt方法中实现以下逻辑

if getGif != nil {
    let arrGif = getGif?.value(forKey: "gifUrl") as! [String]
    let urlString : String = "\(arrGif[0])"
    let image = UIImage.gifImageWithURL(urlString)
    imagePointer?.image = image
    playButton?.isHidden = true
}
else if getPhoto != nil  {
    let arrPhoto = getPhoto?.value(forKey: "photoUrl")  as! [String]
    let urlString = Storage.storage().reference(forURL: arrPhoto[0])
    imagePointer?.downloadImage(urlString: urlString)
    playButton?.isHidden = true
}
elseif getVideo != nil {
    let arrVideo = getVideo?.value(forKey: "videoUrl")  as! [String]
    let urlString = arrVideo[0]
    imagePointer?.downloadImage(urlString: urlString)
    playButton?.isHidden = false
}

如果要在tableView中重新加载照片、视频和gif的一个imageView。然后在重新加载之前使用一个图像数组存储它。这样,您的主要问题挂起或卡住将得到解决。这里的主要问题是每次在滚动时调用和检查表视图中的单元格集合数据

现在,我建议在重新加载表视图之前,将所有照片、GIF和视频(缩略图)作为一个单独的数组来获取

var cacheImages = [UIImage]()

private func fetchData(completionBlock: () -> ()) {
      
        for (index, _) in postiisCollectionDetails.enumerated() {
          
       
            let getGif = postiisCollectionDetails[index].leadDetails?.value(forKey: "gif") as? NSArray
            let getPhoto = postiisCollectionDetails[index].leadDetails?.value(forKey: "photo") as? NSArray
            let getVideo = postiisCollectionDetails[index].leadDetails?.value(forKey: "video") as? NSArray
            
                 if getGif != nil {
                 let arrGif = getGif?.value(forKey: "gifUrl") as! [String]
                 let gifURL : String = "\(arrGif[0])"
                 self.randomList.append(gifURL)
                
                /////---------------------------
                let imageURL = UIImage.gifImageWithURL(gifURL)
                self.cacheImages.append(imageURL!)
                //////=================
                
                }
                else if getVideo != nil {
                    let arrVideo = getVideo?.value(forKey: "videoUrl")  as! [String]
                    let videoURL: String = "\(arrVideo[0])"
                 let videoUrl = URL(string: arrVideo[0])
                 let asset = AVAsset(url:videoUrl!)
                 if let videoThumbnail = asset.videoThumbnail{
                 ////--------------
                 self.cacheImages.append(videoThumbnail)
                 //-----------
                 }
                 self.randomList.append(videoURL)

                }else if getPhoto != nil  {
                  let arrPhoto = getPhoto?.value(forKey: "photoUrl")  as! [String]
                  let photoURL : String = "\(arrPhoto[0])"
            
                /////---------------------------
                    let url = URL(string: photoURL)
                    let data = try? Data(contentsOf: url!)

                    if let imageData = data {
                        let image = UIImage(data: imageData)
                        if image != nil {
                         self.cacheImages.append(image!)
                        }
                        else {
                        let defaultImage: UIImage = UIImage(named:"edit-user-80")!
                        self.cacheImages.append(defaultImage)
                        }
                    }
                  
                 //////=================
                 
                 }
                 else {
                  //-----------------
                 let defaultImage: UIImage = UIImage(named:"edit-user-80")!
                self.cacheImages.append(defaultImage)
                
                //--------------------
                
                }
                
            }
        completionBlock()
 
    }
将所有UIImage作为调用循环的数组获取后。现在在
viewDidLoad
中调用此函数。因此,在获取图像中的所有值之后,尝试像这样调用tableView

override func viewDidLoad() {
    self.fetchData {
       DispatchQueue.main.async 
        self.tableView.reloadData()
      }
     }
}
现在,您至少可以使用
SDWebImage
或任何其他背景图像类或下载方法来调用
tableView cellforRow方法中的那些

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// your cell idetifier & other stuffs
  
          if getVideo != nil {
              imagePointer?.image = cacheImages[indexPath.row]
               playButton?.isHidden = false
           }else {
                imagePointer?.image = cacheImages[indexPath.row]
               // or  get photo with string via SdWebImage
              //   imagePointer?.sd_setImage(with: URL(string: photoURL), placeholderImage: UIImage(named: "edit-user-80"))
                playButton?.isHidden = true
                }
return cell
}

那么应用程序会在一段时间后挂起并崩溃?有什么错误吗?或者它能在一段时间后获取所有数据?@Alan,有时应用程序会在一段时间后挂起并崩溃,有时它能在一段时间后获取所有数据,而scroll
cellForRow
是执行异步任务而不缓存结果的错误位置。而且,与此无关,
viewWithTag
技术已经过时10年了。不要在Swift中使用
NS…
集合类型和
value(forKey
)来详细说明Swift中的最佳实践:-任何调用
value(forKey:)
不应该是必需的。相反,您应该相应地创建数据和设计类的适当结构。-
。viewWithTag
可以用类似的方式替换。使用某些子视图创建您自己的
UIView
子类,并在InterfaceBuilder中相应地分配它们。然后您可以为子VI命名ews,而不是枚举它们。这可以防止打字错误。-在Swift中,您不需要
NSArray
(很少有例外)并且可以使用
Array
谢谢你的回答,但我的问题不仅仅是图像,如果它是唯一的图像,我可以使用你的答案,但我需要在tableview单元格中一次处理图像、gif和视频缩略图(使用一个图像视图)。如果您有解决方案,请建议我。@Aleesha检查更新它可以处理所有三个问题cases@MuhammedWaqas,用cellForRowAt方法回答这个问题对于更多导致tableview挂起或变慢的数据来说不是个好主意。我已经解决了这个问题。谢谢