首页 文章

从URL异步加载图像

提问于
浏览
2

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

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

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

这是我的确切代码 .

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

let API = APIGet()

在UICollectionViewController的viewDidLoad中,我调用:

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

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

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()

 }

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

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

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++
    }
}

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

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()
            }
        }
    }
}

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

我的cellForRowAtIndexPath简单地说:

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
}

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

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

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])


            }
        }
    }
}

我的cellForItemAtIndexPath没有改变 .

1 回答

  • 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:if nil,分配 UIColor.grayColor() ,但如果它's non-nil, assign it to the cell' s image . 这样就维持了排序 . 这不是一个完美的解决方案,但它需要你最少的修改 .

相关问题