从URL异步加载图像

时间:2016-01-06 23:43:08

标签: ios swift asynchronous uicollectionview

我正在使用Swift将图像加载到UICollectionView中。我异步获取图像。用户界面滚动而不是锁定,但仍存在一些问题。我得到一些重复,他们加载到视图相当慢。我创建了一个视频来准确显示问题:

https://www.youtube.com/watch?v=nQmAXpbrv1w&feature=youtu.be

我的代码主要来自此问题的接受答案:Loading/Downloading image from URL on Swift

这是我的确切代码。

在我的UICollectionViewController类声明下面,我声明了一个变量:

 override func viewDidLoad() {
    API.getRequest()
    API.delegate = self
}

在UICollectionViewController的viewDidLoad中,我调用:

  func getRequest() {
let string = "https://api.imgur.com/3/gallery/t/cats"
let url = NSURL(string: string)
let request = NSMutableURLRequest(URL: url!)
request.setValue("Client-ID \(cliendID)", forHTTPHeaderField: "Authorization")
let session = NSURLSession.sharedSession()
let tache = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
    if let antwort = response as? NSHTTPURLResponse {
        let code = antwort.statusCode
        print(code)
        do {

            let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)

            dispatch_async(dispatch_get_main_queue(), { () -> Void in
                if let data = json["data"] as? NSDictionary {
                    if let items = data["items"] as? NSArray {
                        var x = 0
                        while x < items.count {
                            if let url2 = items[x]["link"] {
                                self.urls.append(url2! as! String)


                            }

                            x++
                        }
                        self.delegate?.retrievedUrls(self.urls)

                    }
                }

            })



        }
        catch {

        }
    }
   }
 tache.resume()

 }

在我的APIGet类中,我有函数getRequest:

  func retrievedUrls(x: [String]) {
    //
    self.URLs = x
    var y = 0
    while y < self.URLs.count {
        if let checkedURL = NSURL(string: self.URLs[y]) {
            z = y
            downloadImage(checkedURL)

        }
        y++
    }
}

上面的函数正是它应该做的:它从端点获取JSON,并从JSON获取图像的URL。检索完所有URL后,它会将它们传递回UICollectionViewController。

现在,我的代码在UICOllectionViewController中。这是从APIGet类接收URL的函数。:

   func getDataFromUrl(url:NSURL, completion: ((data: NSData?, response: NSURLResponse?, error: NSError? ) -> Void)) {
    NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
        completion(data: data, response: response, error: error)
        }.resume()

}


func downloadImage(url: NSURL){
    getDataFromUrl(url) { (data, response, error)  in
        dispatch_async(dispatch_get_main_queue()) { () -> Void in
            guard let data = data where error == nil else { return }

            if let image = UIImage(data: data) {
                self.images.append(image)
                self.collectionView?.reloadData()
            }
        }
    }
}

然后,这两个函数负责下载图像:

  override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell1", forIndexPath: indexPath) as! cell1

    if indexPath.row > (images.count - 1) {
        cell.image.backgroundColor = UIColor.grayColor()
    }
    else {
    cell.image.image = images[indexPath.row]
    }


    return cell
}

当我在加载图像后调用self.collectionView.reloadData()时,每次加载新图像时,collectionView都会闪烁并且单元格会移动。但是,如果我根本不调用self.collectionView.reloadData(),那么在加载图像后,collectionView永远不会重新加载,并且单元格保持为空。我怎么能绕过这个?我希望图像只是加载到它们各自的单元格中,而不是使用collectionView闪烁/闪烁或让单元格移动,或者获得临时副本。

我的cellForRowAtIndexPath只是:

    func retrievedUrls(x: [String]) {
    //
    self.URLs = x
    self.collectionView?.reloadData()
    var y = 0
    while y < self.URLs.count {
        if let checkedURL = NSURL(string: self.URLs[y]) {

            downloadImage(checkedURL, completion: { (image) -> Void in

            })

        }
        y++
    }
}

func getDataFromUrl(url:NSURL, completion: ((data: NSData?, response: NSURLResponse?, error: NSError? ) -> Void)) {
    NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
        completion(data: data, response: response, error: error)
        }.resume()

}

func downloadImage(url: NSURL, completion: (UIImage) -> Void) {
    getDataFromUrl(url) { (data, response, error) -> Void in
        dispatch_async(dispatch_get_main_queue()) { () -> Void in
            guard let data = data where error == nil else { return }

            if let image = UIImage(data: data) {
                self.images.append(image)
                let index = self.images.indexOf(image)
                let indexPath = NSIndexPath(forRow: index!, inSection: 0)
                self.collectionView?.reloadItemsAtIndexPaths([indexPath])


            }
        }
    }
}

这是我第一次涉足iOS网络,所以我不想默认使用图书馆(alamofire,AFNetowrking,SDWebImage等),所以我可以从头开始学习如何做到这一点。感谢您阅读所有这些!

编辑:这是我的最终代码,主要来自以下答案:

{{1}}

我的cellForItemAtIndexPath没有改变。

1 个答案:

答案 0 :(得分:2)

我不确定你对闭包等有多熟悉,但是你正在使用它们作为NSURLSession的一部分,所以我会假设熟悉过去。

在您的函数downloadImage(_:)中,您可以重写它以获得签名

func downloadImage(url: NSURL, completion: (UIImage) -> Void)

这使您能够在通话时决定该图像还有什么用处。 downloadImage将被修改为:

func downloadImage(url: NSURL, completion: (UIImage) -> Void) {
  getDataFromUrl(url) { (data, response, error) -> Void in
    dispatch_async(dispatch_get_main_queue()) { () -> Void in
      guard let data = data where error == nil else { return }

      if let image = UIImage(data: data) {
        self.images.append(image)
        completion(image)
      }
    }
  }
}

我们改变的是,我们将图像传递给新定义的完成块。

现在,为什么这有用? retreivedUrls(_:)可以通过以下两种方式之一进行修改:要么直接将图像分配给单元格,要么在相应的索引处重新加载单元格(以较容易者为准;直接分配保证不会引起闪烁,但在我的经验,重新加载一个单元格也没有。)

func retrievedUrls(x: [String]) {
  //
  self.URLs = x
  var y = 0
  while y < self.URLs.count {
    if let checkedURL = NSURL(string: self.URLs[y]) {
      z = y
      downloadImage(checkedURL, completion: { (image) -> Void in
        // either assign directly:
        let indexPath = NSIndexPath(forRow: y, inSection: 0)
        let cell = collectionView.cellForItemAtIndexPath(indexPath) as? YourCellType
        cell?.image.image = image
        // or reload the index
        let indexPath = NSIndexPath(forRow: y, inSection: 0)
        collectionView?.reloadItemsAtIndexPaths([indexPath])
      })

    }
    y++
  }
}

请注意,我们使用完成块,只有在下载图像并将其附加到images数组后(同时调用collectionView?.reloadData()),才会调用完成块。

然而!您需要注意的是,在实现中,您要将图像附加到数组中,但可能无法保持与URL相同的顺序!如果某些图像的下载顺序与您传入URL的顺序完全相同(很可能,因为它都是异步),它们会混淆不清。我不知道你的应用程序做了什么,但可能你会希望将图像分配给单元格的顺序与给出的URL相同。我的建议是将您的images数组更改为Array<UIImage?>类型,并使用相同数量的nils填充它,然后修改cellForItemAtIndexPath以检查图像是否为nil:如果为nil,则指定UIColor.grayColor(),但如果它为非nil,则将其分配给单元格image。这样你维持订购。它不是一个完美的解决方案,但它需要你最少的修改。