我正在使用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没有改变。
答案 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
。这样你维持订购。它不是一个完美的解决方案,但它需要你最少的修改。