Collection View Loads Choppy - ios

I have a collection view that is loading data from an API. The Collection View works fine, except it only loads images on scroll. I've tried loading the images async and it makes a minimal difference. I want all the images to be loaded or have a preloading image until the image loads? I'm using Haneke to load images from a URL. Here's the code I'm using in my collection view:
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.tableData.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: ProductsViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("productViewCell", forIndexPath: indexPath) as! ProductsViewCell
let rowData = tableData[indexPath.row]
cell.backgroundColor = UIColor.whiteColor()
for (key,value) in rowData {
cell.productPrice.text = value["merged"][0]["variants"][0]["price"].string
cell.productName.text = value["merged"][0]["title"].string
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
if let imageString = value["merged"][0]["images"][0]["src"].string {
let url = NSURL(string: imageString)
dispatch_async(dispatch_get_main_queue(), {
cell.productImage.contentMode = .ScaleAspectFill
cell.productImage.hnk_setImageFromURL(url!)
})
}
})
if let variantData = value["merged"][0]["variants"].array {
var sum = 0
for variant in variantData {
sum += variant["inventory_quantity"].int!
}
if sum <= 0 {
cell.soldOut.hidden = false
} else {
cell.soldOut.hidden = true
}
}
}
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print("Cell \(indexPath.row) selected")
}

You want to do all of the downloading of the main thread then once the image or image data is available dispatch to the main queue to update the UI. Here you're going in the right direction but you're downloading on the main thread. Change this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
if let imageString = value["merged"][0]["images"][0]["src"].string {
let url = NSURL(string: imageString)
dispatch_async(dispatch_get_main_queue(), {
cell.productImage.contentMode = .ScaleAspectFill
cell.productImage.hnk_setImageFromURL(url!)
})
}
})
To:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
if let imageString = value["merged"][0]["images"][0]["src"].string {
let url = NSURL(string: imageString)
let imageData = NSData(contentsOfURL: imageString)
let image = UIImage(data: imageData)
dispatch_async(dispatch_get_main_queue(), {
cell.productImage.contentMode = .ScaleAspectFill
cell.productImage.image = image
})
}
})

Related

Loading Aync Gifs to Scrolling CollectionView

The gifs I fetch from the Giphy API returns correctly and in fact loads properly to the uicollectionview using SwiftGif.
The issue only surfaces when I scroll immediately, the uicollectionview loads either duplicate gifs or gifs that are in the incorrect index. I understand this is probably a timing issue with the delay in rendering the gif and loading the gif to the cell.
Any guidance would be appreciated as asynchronous operations are something I'm still unfamiliar with..
Also any best practices for handling gifs would be appreciated if there are any flags in the code below, specifically to support speed/memory usage.
I've tried placing various checks like seeing if the initially passed gif url is the same at the point it's loaded, and also setting the image to nil every time cellForItemAt is fired, but to no avail. Couldn't find existing threads that clearly resolved this issue as well.
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var gifCollectionView: UICollectionView!
var gifUrls: [String] = []
var gifImages: [String: UIImage] = [:]
func fetchGiphs() {
let op = GiphyCore.shared.search("dogs", media: .sticker) { (response, error) in
guard error == nil else {
print("Giphy Fetch Error: ", error)
return
}
if let response = response, let data = response.data, let pagination = response.pagination {
for result in data {
if let urlStr = result.images?.downsized?.gifUrl {
self.gifUrls.append(urlStr)
}
}
if !self.gifUrls.isEmpty {
DispatchQueue.main.async {
self.gifCollectionView.reloadData()
}
}
} else {
print("No Results Found")
}
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return gifUrls.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! GifCell
let passedUrlString = gifUrls[indexPath.item]
cell.imageView.image = nil
if let image = gifImages[gifUrls[indexPath.item]] {
DispatchQueue.main.async {
cell.imageView.image = image
cell.activityIndicator.isHidden = true
}
} else {
cell.activityIndicator.isHidden = false
cell.activityIndicator.startAnimating()
DispatchQueue.global(qos: .default).async {
let gifImage = UIImage.gif(url: self.gifUrls[indexPath.item])
DispatchQueue.main.async {
if passedUrlString == self.gifUrls[indexPath.item] {
cell.activityIndicator.stopAnimating()
cell.activityIndicator.isHidden = true
cell.imageView.image = gifImage
self.gifImages[self.gifUrls[indexPath.item]] = gifImage
}
}
}
}
return cell
}
}
class GifCell: UICollectionViewCell {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
}
As you know, the cell may be reused when image loading completed.
You need to check if it is reused or not. Your passedUrlString == self.gifUrls[indexPath.item] does not work for this purpose.
Maybe, giving a unique ID for each cell would work:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! GifCell
let uniqueId = Int.random(in: Int.min...Int.max) //<-practically unique
cell.tag = uniqueId //<-
cell.imageView.image = nil
if let image = gifImages[gifUrls[indexPath.item]] {
cell.imageView.image = image
cell.activityIndicator.isHidden = true
} else {
cell.activityIndicator.isHidden = false
cell.activityIndicator.startAnimating()
DispatchQueue.global(qos: .default).async {
let gifImage = UIImage.gif(url: self.gifUrls[indexPath.item])
DispatchQueue.main.async {
if cell.tag == uniqueId { //<- check `cell.tag` is not changed
cell.activityIndicator.stopAnimating()
cell.activityIndicator.isHidden = true
cell.imageView.image = gifImage
self.gifImages[self.gifUrls[indexPath.item]] = gifImage
}
}
}
}
return cell
}
Assuming you are not using tag for other purposes.
Please try.

Show wrong images in UICollectionView

there! I need your help. I have UIImage inside UICollectionView which lying inside UITableView. When I get data from API at the first time it shows images right, but when I start to scroll down and come back, it shows wrong images. My code looks like this:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! AllPostsOfUserCollectionViewCell
let post = allPostsOfUserArray[collectionView.tag]
if post.imageLinks.count != 0 {
let imageLink = post.imageLinks[indexPath.row]
if imageLink.imageLink != nil {
let url = URL(string: imageLink.imageLink!)
cell.imageOfAnimalInCollectionView.sd_setImage(with: url!, placeholderImage: UIImage(named: "App-Default"),options: SDWebImageOptions(rawValue: 0), completed: { (image, error, cacheType, imageURL) in
})
}
}
return cell
}
My model looks like this:
class UserContent {
var name = ""
var imageLinks = [UserImages]()
init(name: String, imageLinks: [UserImages]?) {
self.name = name
if imageLinks != nil {
self.imageLinks = imageLinks!
}
}
init() { }
deinit {
imageLinks.removeAll()
}
}
class UserImages {
var imageLink: String?
init(imageLink: String?) {
self.imageLink = imageLink
}
init() { }
deinit {
imageLink = ""
}
}
What I do in UITableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let index = indexPath.row
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! AllPostsOfUserTableViewCell
cell.collectionView.tag = index
let post = allPostsOfUserArray[index]
if post.imageLinks.count == 0 {
cell.collectionView.isHidden = true
} else {
cell.collectionView.isHidden = false
}
return cell
}
UPD: I added cell.collectionView.reloadData() to func tableView(cellForRowAt indexPath). Now it works fine.
Could you try this code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! AllPostsOfUserCollectionViewCell
cell.imageOfAnimalInCollectionView.image = UIImage(named: "App-Default")
let post = allPostsOfUserArray[collectionView.tag]
if post.imageLinks.count != 0 {
let imageLink = post.imageLinks[indexPath.row]
if imageLink.imageLink != nil {
let url = URL(string: imageLink.imageLink!)
cell.imageOfAnimalInCollectionView.sd_setImage(with: url!, placeholderImage: UIImage(named: "App-Default"),options: SDWebImageOptions(rawValue: 0), completed: { (image, error, cacheType, imageURL) in
})
}
}
return cell
}
This problem is something like race condition.
You can reference my answer in similair question
You might need to find another module to download image and set to cell manually.
cell.imageOfAnimalInCollectionView.image = nil
if post.imageLinks.count != 0 {
let imageLink = post.imageLinks[indexPath.row]
if imageLink.imageLink != nil {
let url = URL(string: imageLink.imageLink!)
cell.imageOfAnimalInCollectionView.sd_setImage(with: url!, placeholderImage: UIImage(named: "App-Default"),options: SDWebImageOptions(rawValue: 0), completed: { (image, error, cacheType, imageURL) in
})
}
}
(Not tested, but should work. You can also use sd_image's method to assign an empty url, with a placeholder image to just show the placeholder image. Or just give an empty url string '' without the placeholder image)
Whenever you scroll, the cells are re-used, and the image assigned to the cell reappears because it is still on the cell.
You have to remove those images by using else clause for every if where you are assigning an image to the imageView.
Hope this helps.
You have to write else conditions like Below ..
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! AllPostsOfUserCollectionViewCell
let post = allPostsOfUserArray[collectionView.tag]
if post.imageLinks.count != 0 {
let imageLink = post.imageLinks[indexPath.row]
if imageLink.imageLink != nil {
let url = URL(string: imageLink.imageLink!)
cell.imageOfAnimalInCollectionView.sd_setImage(with: url!, placeholderImage: UIImage(named: "App-Default"),options: SDWebImageOptions(rawValue: 0), completed: { (image, error, cacheType, imageURL) in
})
}
else{
cell.imageOfAnimalInCollectionView = "defaultImage"
}
}
else{
cell.imageOfAnimalInCollectionView = "defaultImage"
}
return cell
}
Reason
after dequeueing cell cellectionView reuses the created cell for all next cells so if you not write an else condition or not use prepareForReuse method it will always data of previous cell that is in its cache.

How to download images async for collectionView inside TableViewCell?

I did a grid (collectionView) inside a tableViewCell, the problem is loading different images per cell. Make a Json like this:
{
{
"name": "Vegetales"
"images": { imagesURLStrings }
},
{
"name": "Frutas"
"images": { imagesURLStrings }
},
}
I use this page for custom the view and this other to make the async download.
I think the problem is because, when I try to defined the quantity of cells for the collectionView inside the tableviewCell, the assignation its wrong, its not working, and I don't know how to fixed.
The code for download the images:
func loadImages() {
var section = 0
var row = 0
while (section < searchDataresults.count) {
for i in searchDataresults[section].images {
let key = section * 10 + row
let imageUrl = i
let url:URL! = URL(string: imageUrl)
task = session.downloadTask(with: url, completionHandler: { (location, response, error) -> Void in
if let data = try? Data(contentsOf: url){
// 4
DispatchQueue.main.async(execute: { () -> Void in
// 5
// Before we assign the image, check whether the current cell is visible
let img:UIImage! = UIImage(data: data)
saveImage(image: img, name: String(key))
})
}
})
task.resume()
row += 1
}
section += 1
row = 0
}
}
}
And the code were I put the images on the collectionView, remembering that it is inside a tableViewCell, so the quantity of cells have to change depending of the images.count of the json.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return searchDataresults[cellLoad].images.count
}
internal func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellImage", for: indexPath as IndexPath)
let key = cellLoad * 10 + indexPath.row
if let img = loadImage(name: String(key)) {
let imageView = UIImageView(image: img)
imageView.frame = cell.frame
imageView.bounds = cell.bounds
imageView.center = cell.center
cell.contentView.addSubview(imageView)
print(key)
} else {
let imageView = UIImageView(image: UIImage(named: "emptyImg"))
imageView.frame = cell.frame
imageView.bounds = cell.bounds
imageView.center = cell.center
cell.contentView.addSubview(imageView)
}
return cell
}
I really appreciate your help!
subclass UICollectionviewCell and reset the content of your collection view cell
override func prepareForReuse() {
super.prepareForReuse()
self.customImageview.image = nil
}

How to call image urls and image view in cell for index path function for a collection view

I want to asynchronously dispatch 8 image urls in a collection view. I have created a class for collection view cell and also made an outlet to imageview in it. Now I want to configure the imageview from main view controller. Here is the code
let reuseIdentifier = "PhotosCollectionViewCell" // also enter this string as the cell identifier in the storyboard
var items = ["1", "2", "3", "4", "5", "6", "7", "8"]
// tell the collection view how many cells to make
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.items.count
}
// make a cell for each cell index path
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! PhotosCollectionViewCell
cell.imageView = imageView
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
// handle tap events
print("You selected cell #\(indexPath.item)!")
}
func loadImage() {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
let urlString = "http://charcoaldesign.co.uk/AsyncImageView/Forest/IMG_0352.JPG"
let url = NSURL(string: urlString)
let data = NSData(contentsOfURL: url!)
dispatch_async(dispatch_get_main_queue(), {
self.imageView.image = UIImage(data: data!)
// self.items[0] = (data as? String)!
})
}
}
}
Here is a extension to make things more easy.
extension UIImageView {
func downloadImageFrom(link link:String, contentMode: UIViewContentMode) {
NSURLSession.sharedSession().dataTaskWithURL( NSURL(string:link)!, completionHandler: {
(data, response, error) -> Void in
dispatch_async(dispatch_get_main_queue()) {
self.contentMode = contentMode
if let data = data { self.image = UIImage(data: data) }
}
}).resume()
}
}
cell.imageView.downloadImageFrom(link: imageLinkArray[indexPath.row], contentMode: UIViewContentMode.ScaleAspectFit) //set your image from link array.
Also you can look below url for more help.
how to implement lazy loading of images in table view using swift
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! PhotosCollectionViewCell
cell.imageView = imageView
loadImage(imageView, urlPAssed: urlString)
return cell
}
func loadImage(imageViewObj : UIImageView , urlPAssed : NSString) {
//let urlString = "http://charcoaldesign.co.uk/AsyncImageView/Forest/IMG_0352.JPG"
let url = NSURL(string: urlPAssed as String)
NSURLSession.sharedSession().dataTaskWithURL(url!) { (data, response, error) in
if(error==nil)
{
dispatch_async(dispatch_get_main_queue(), { () -> Void in
imageViewObj.image = UIImage(data: data!)
})
}
else
{
imageViewObj.image = UIImage(named: "dummy")
}
}.resume()
}

Why after "reloadCollectionViewDataAtIndexPath(index)", some cell disappear?

in the func
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
I use
if let imageData_ = imageData{
cell.setImage(UIImage(data: imageData_))
}
else{
cell.setImage(nil)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { () -> Void in
var Data = self.getImageFromModel(point, index: indexPath.item)
if let Data_ = Data{
self.imageCache.setObject(Data_, forKey: NSString(format: "%d", indexPath.item))
NSLog("Download Image for %d", indexPath.item)
self.reloadCollectionViewDataAtIndexPath(indexPath)
}
else{
println("nil Image")
}
})
}
func reloadCollectionViewDataAtIndexPath(indexPath:NSIndexPath){
var indexArray = NSArray(object: indexPath)
self.collectionView.reloadItemsAtIndexPaths(indexArray)
}
to load image from web,but after loading all the picture,some cells that are out of the initial scene disappear(below the initial scene,can scroll the view)
but if I scroll the view to make the missing cells visible before load all the images completely,everything is fine...
where is the problem..?

Resources