How to download images async for collectionView inside TableViewCell? - uitableview

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
}

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.

CollectionCell displays wrong image?

I have a TableView with cells, and one cell is holding a CollectionView.
Inside the CollectionView, I have cells with UIImageViews.
If I add new elements to the datasource while the CollectionView is visible then it works fine.
But if I scroll down in the TableView, add the new elements then scroll up, then even though it adds the new cells, they are displaying the wrong image.
Video: https://youtu.be/QwvMv2xaaAI
Code:
MainViewController(Not the whole)
func addNewPhotos(newPhotosArray: [Photo]){
var collectionViewInserts : [IndexPath] = []
for (i in 0...newPhotosArray.count) {
// I add the new photos to the datasource
PhotosStore.shared.photos.insert(newPhotosArray[i], at: 0)
// Then save the indexPath what needs to be inserted
collectionViewInserts.insert(IndexPath(row: i, section: 0), at: 0)
}
if let cell = self.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? PhotosCell {
cell.photosCollectionView.performBatchUpdates({
cell.photosCollectionView.insertItems(at: collectionViewInserts)
}, completion: nil)
}
}
extension MainViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return PhotosStore.shared.photos.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as! PhotoCell
cell.photoImageView.downloadedFrom(link: (appSettings.url + "/resources/img/wp/prev/" + PhotosStore.shared.photos[indexPath.item].fileName))
return cell
}
}
PhotosCell:
import UIKit
class PhotosCell : UITableViewCell{
#IBOutlet weak var photosCollectionView : UICollectionView!
}
extension PhotosCell {
func setCollectionViewDataSourceDelegate<D: UICollectionViewDataSource & UICollectionViewDelegate>(_ dataSourceDelegate: D, forRow row: Int) {
// IF I PLACE A .reloadData() HERE, THEN IT WORKS BUT THEN THE CELL FLICKERS/JUMPS WHEN APPEARING ON SCREEN
let itemSize = 70
photosCollectionView.delegate = dataSourceDelegate
photosCollectionView.dataSource = dataSourceDelegate
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = UICollectionViewScrollDirection.horizontal
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: itemSize, height: itemSize)
photosCollectionView.setCollectionViewLayout(layout, animated: true)
photosCollectionView.tag = row
photosCollectionView.setContentOffset(photosCollectionView.contentOffset, animated:false) // Stops collection view if it was scrolling.
photosCollectionView.reloadData()
}
var collectionViewOffset: CGFloat {
set { photosCollectionView.contentOffset.x = newValue }
get { return photosCollectionView.contentOffset.x }
}
}
What do I wrong? I do update the datasource correctly, I do perform batch updates on the collection view to insert the correct cells..
Updated details:
MainViewController:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Photos on top
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "PhotosCell", for: indexPath) as! PhotosCell
cell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row)
cell.collectionViewOffset = storedPhotosCollectionViewOffset[indexPath.row] ?? 0
return cell
}
... other cells ...
}
Extension to download images: (I'm sure that's not the problem but just in case)
extension UIImageView {
func downloadedFrom(url: URL, contentMode mode: UIViewContentMode = .scaleAspectFit) {
image = nil
if let cachedImage = ImageCache.shared.loadCachedImage(url: url) {
image = cachedImage
return
}
contentMode = mode
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else {
DispatchQueue.main.async {
self.image = UIImage(named: "imageMissing")
}
return
}
DispatchQueue.main.async {
self.image = image
ImageCache.shared.cacheImage(image: image, url: url)
}
}.resume()
}
func downloadedFrom(link: String, contentMode mode: UIViewContentMode = .scaleAspectFit) {
guard let url = URL(string: link) else { return }
return downloadedFrom(url: url, contentMode: mode)
}
}
First in cellForRowAt
cell.photosCollectionView.reloadData()
return cell
Second you have to note that the image is downloaded ( consider a dummy image for the imageView or set a background to it ) every scroll so use SDWebImage

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

Collection View Loads Choppy

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

uiimageview animation stops when user touches screen

I have a UImageview with animated image.
i am adding the uiimageview in code and its a part of a CollectionViewCell
When the user touches the cell the animation stops, why does this happen?
code:
var images: [UIImage] = []
for i in 0...10 {
images.append(UIImage(named: "image\(i)"))
}
let i = UIImageView(frame: CGRect(x: xPos, y: yPos, width: 200, height: 200))
i.animationImages = images
i.animationDuration = 0.5
i.startAnimating()
i.contentMode = UIViewContentMode.Center
i.userInteractionEnabled = false
self.addSubview(i)
If you don't want any interaction then following will be the fastest way to resolve this issue:
collectionView.allowsSelection = false
In your custom collection view cell class, write following methods to fix issue
func setSelected(selected:Bool) {
}
func setHighlighted(higlighted:Bool) {
}
Swift 4.0 Version:
override open var isSelected: Bool
{
set {
}
get {
return super.isSelected
}
}
override open var isHighlighted: Bool
{
set {
}
get {
return super.isHighlighted
}
}
Overriding isSelected, isHighlighted with empty setter will solve this issue, but it will lose those two properties to be set. I was able to solve this issue by calling imageView.startAnimating() at didSelectItemAt in UICollectionViewDelegate.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item = items[indexPath.item]
if item.hasGIF {
let cell = collectionView.cellForItem(at: indexPath) as! ItemCell
cell.imageView.startAnimating()
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let item = items[indexPath.item]
if item.hasGIF {
let cell = collectionView.cellForItem(at: indexPath) as! ItemCell
cell.imageView.startAnimating()
}
}
In my case, I can't disable selection or use any of the solutions posted before here. I do not need to highlight the cell so I disabled it through the delegate method below to return false which prevented the stopAnimating() method from being called. This was an issue I encountered when using the AnimatedImageView of KingFisher used in a UICollectionViewCell.
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
return false
}
An array of UIImage objects to use for an animation. var highlightedAnimationImages: [UIImage]? An array of UIImage objects to use for an animation when the view is highlighted. The amount of time it takes to go through one cycle of the images.
I created a custom UIImageView class and only stopped animation if I want it to stop.
class GifImageView: UIImageView {
private var stop = false
static func getAudioGif(_ size: CGFloat) -> GifImageView {
return fromGif(frame: CGRect(x: 0, y: 0, width: size, height: size), resourceName: "audio")!
}
override func stopAnimating() {
if stop {
stop = false
super.stopAnimating()
}
}
func stopForReal() {
stop = true
stopAnimating()
}
func fromGif(frame: CGRect, resourceName: String) -> GifImageView? {
guard let path = Bundle.main.path(forResource: resourceName, ofType: "gif") else {
print("Gif does not exist at that path")
return nil
}
let url = URL(fileURLWithPath: path)
guard let gifData = try? Data(contentsOf: url),
let source = CGImageSourceCreateWithData(gifData as CFData, nil) else { return nil }
var images = [UIImage]()
let imageCount = CGImageSourceGetCount(source)
for i in 0 ..< imageCount {
if let image = CGImageSourceCreateImageAtIndex(source, i, nil) {
images.append(UIImage(cgImage: image))
}
}
let gifImageView = GifImageView(frame: CGRect(x: 0, y: 0, width: frame.width, height: frame.height))
gifImageView.animationImages = images
return gifImageView
}
}
In TableView Use code below can solve touche cancel, touche moved and so on
- (void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath {
[cell startAnimation];
}

Resources