I make a network call in ViewDidLoad to get objects (first 25), then I make another call in willDisplayCell to get the rest of the objects. I'm Using PINReMoteImage in the code below. It works, but the problem is that as you scroll through the collection view, a cell will have one picture then another picture will appear over it. How can I improve this UI? I thought updateWithProgress was supposed to deal with this by using a blur until the image is loaded but it doesn't seem to be working?
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! PinCollectionViewCell
if let pinImageURL = self.pins[indexPath.row].largestImage().url {
cell.pinImage?.pin_updateWithProgress = true
cell.pinImage?.pin_setImageFromURL(pinImageURL, completion: ({ (result : PINRemoteImageManagerResult) -> Void in
if let image = result.image {
self.imageArray.append(image)
}
}))
}
The problem is that I'm reusing cells, but I wasn't resetting the image. So I simply added cell.pinImage?.image = nil to the above. Once I found out that was the problem I added an UIActivityViewIndicatorViewto the cell and stop it when the image comes in.
Related
I am stuck with a problem. I want to populate a tableview with some text and a profile image that can change i use this function for it.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: CommonCellView!
cell = self.tableView.dequeueReusableCellWithIdentifier("CommonCellView") as! CommonCellView
cell.nameLabel.text = self.userCollection[indexPath.row].display_name
cell.companyLabel.text = self.userCollection[indexPath.row].user_organisation
cell.profileImage.hnk_setImageFromURL(NSURL(string: self.userCollection[indexPath.row].profile_picture)!)
self.makeImageViewCircular(cell.profileImage.layer, cornerRadius: cell.profileImage.frame.height)
cell.profileImage.clipsToBounds = true
return cell
}
nothing to suprising here. But when i change my own profile picture then i send it to the API and revist this function it shows the cached image. So i tought i might try something a bit diffent why not get all the images for every cell using Alamofire.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: CommonCellView!
cell = self.tableView.dequeueReusableCellWithIdentifier("CommonCellView") as! CommonCellView
cell.nameLabel.text = self.userCollection[indexPath.row].display_name
cell.companyLabel.text = self.userCollection[indexPath.row].user_organisation
cell.profileImage.image = UIImage()
//getting the cell image
Alamofire.request(.GET, self.userCollection[indexPath.row].profile_picture)
.response {(request, response, avatarData, error) in
let img = UIImage(data: avatarData!)
cell.profileImage.image = img
}
self.makeImageViewCircular(cell.profileImage.layer, cornerRadius: cell.profileImage.frame.height)
cell.profileImage.clipsToBounds = true
return cell
}
this works to a point where user scrolles very fast the image of a different user will be shown until the request gets fulfilled. Okey so make that happen somewere else and use an array for the images. I also tried that. but because its async the images would go into the array in the wrong order. So back to HanekeSwift. I read the documentation and saw i had a cache on disk but i could not clear or delete it.
to clear the cache i also tried:
NSURLCache.sharedURLCache().removeAllCachedResponses()
but i did not do a thing. it works in the Alamofire situation but its not a good solution either.
I want to use hanekeSwift because HanekeSwift is fast enough to get all the images. but i want to clear the cache everytime the contoller loads.
Any suggestions would be appreciated!
Cees
I found a the problem.
First i was running an older version of the pod. So after updating i could use the function.
Shared.imageCache.removeAll()
after you import haneke into the controller.
the final pice of code looked like this.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: CommonCellView!
cell = self.tableView.dequeueReusableCellWithIdentifier("CommonCellView") as! CommonCellView
cell.nameLabel.text = self.userCollection[indexPath.row].display_name
cell.companyLabel.text = self.userCollection[indexPath.row].user_organisation
cell.profileImage.image = UIImage()
//getting the cell image
cell.profileImage.hnk_setImageFromURL(NSURL(string: self.userCollection[indexPath.row].profile_picture)!)
//deleting it from cache
Shared.imageCache.removeAll()
self.makeImageViewCircular(cell.profileImage.layer, cornerRadius: cell.profileImage.frame.height)
cell.profileImage.clipsToBounds = true
return cell
}
it works now but removing the cache all the time an other cell gets filled seems a bit overkill.
I am creating a UICollectionView that initially downloads images from an AWSS3 bucket, then caches the images for later access. The problem is that since the download takes some time, when the user scrolls the UICollectionView more downloads are queued up. The first batch of downloads finishes and loads into the newly visible cells, then the second batch of downloads finishes and replaces the same cells.
This results in images appearing in cells they should not. I have attempted the solutions from similar posts but have not found one works.
Here is a simplified version of what I have tried
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell = self.collectionView.dequeueReusableCellWithReuseIdentifier("imageCell", forIndexPath: indexPath) as! GroupImageCell
cell.imageView.image = nil
getPhotoForCell(cell, indexPath)
}
func getPhotoForCell(cell: GroupImageCell, idx: NSIndexPath)
{
#use AWSS3downloadrequest
#once results are in
if (task.result != nil) {
#get body of result, then url, then data
let data = NSData(data: task.result.body as! NSURL)
let image = UIImage(data: data)
if (collectionView.cellForItemAtIndexPath(idx) != nil)
{
dispatch_async(dispatch_get_main_queue(), {
cell.imageView.image = image
})
}
}
}
You can fix this issue by storing the images in your model object that is used for each cell. Meaning, instead of
dispatch_async(dispatch_get_main_queue(), {
cell.imageView.image = image
})
Do something like this
dispatch_async(dispatch_get_main_queue(), {
items[indexPath.row].image = image
})
This way you will guarantee to render the correct image when you call from your cellForItemAtIndexPath method.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell = self.collectionView.dequeueReusableCellWithReuseIdentifier("imageCell", forIndexPath: indexPath) as! GroupImageCell
cell.imageView.image = items[indexPath.row].image
}
I'm following a site to help learn swift and I'm getting confused about this part right here. Basically we added the if cell.imageview.image == nil statement so hat when the collection view loads and you scroll the image doesn't reload the filters. What I don't understand is if you scroll down a cell is reused for the bottom row, now why if I scroll back up doesn't it have to reload the filter? is that data saved somewhere so when I scroll up the properties don't have to repopulate? and If thats the case why would I have to use that if statement at all?
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MyCell", forIndexPath: indexPath) as! FilterCell
if cell.imageView.image == nil {
cell.imageView.image = placeholder
let filterQueue: dispatch_queue_t = dispatch_queue_create("filter queue", nil)
dispatch_async(filterQueue, { () -> Void in
let filterImage = self.filteredImageFromImage(self.thisFeeditem.thumbNail, filter: self.filters[indexPath.row])
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.imageView.image = filterImage
})
})
}
return cell
}
When a cell is reused, a cell object that was already allocated is just used again. Any properties or data that was set to it will remain.
When you scroll back up, the cell has already had it's image set, and so it won't reload the new filtered image.
Cells can be reused whether you scroll up or down. You should assume the cell returned is a cached version for a different item. Therefore it may already be bound with another cell's data and you'd want to always rebind the cell with the proper items' data.
I just got into ios swift and are now trying to switch between listview and bigger listview.
What I have now is a normal listview. What I want to do is to create a button that toggles between listview and "bigger" listview eg bigger image in every list item like in Instagram.
Is this done by creating two seperate viewcontrollers?
Your question is not clear at all.
It is clearly possible to create that with two ViewControllers performing
a Segue passing the Object selected to the next view.
Alternative , if what you are using is a ScrollView , you can use the existing zoom methods (zoomToRect).
What I did to make it work is have 2 UIButton connect with 2 func within the main collectionview class as describe below
// Use to define whether displaying grid view or block view
// useful when you use nib files for cells (see the 3rd code example)
// true by default
var isGridView = true
loadGridView () {
isGridView = true
collectionView?.performBatchUpdates({
// load or setup for gridlayout
}, completion: nil)
collectionView?.reloadData()
}
and
loadBlockView () {
isGridView = false
collectionView?.performBatchUpdates({
// load or setup for blocklayout
}, completion: nil)
collectionView?.reloadData()
}
For instance, you can visit this article for more information
!!! If you're using nib for cells, you will need to register all nibs and its class also be aware of
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell:UICollectionViewCell
if(isGridView) {
let gridCell = collectionView.dequeueReusableCellWithReuseIdentifier(cellId, forIndexPath: indexPath) as! CustomGridViewCellClass
// some setup
cell = gridCell
} else {
let blockCell = collectionView.dequeueReusableCellWithReuseIdentifier(cellId, forIndexPath: indexPath) as! CustomBlockViewCellClass
// some setup
cell = blockCell
}
return cell
}
I have a UITableView with UICollectionView insight every table view cell. I use the UICollectionView view as a gallery (collection view with paging). My logic is like this:
Insight the method
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// This is a dictionary with an index (for the table view row),
// and an array with the url's of the images
self.allImagesSlideshow[indexPath.row] = allImages
// Calling reloadData so all the collection view cells insight
// this table view cell start downloading there images
myCell.collectionView.reloadData()
}
I call collectionView.reloadData() and in the
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// This method is called from the cellForRowAtIndexPath of the Table
// view but only once for the visible cell, not for the all cells,
// so I cannot start downloading the images
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! PhotoCollectionCell
if self.allImagesSlideshow[collectionView.tag] != nil {
var arr:[String]? = self.allImagesSlideshow[collectionView.tag]!
if let arr = arr {
if indexPath.item < arr.count {
var imageName:String? = arr[indexPath.item]
if let imageName = imageName {
var escapedAddress:String? = imageName.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
if let escapedAddress = escapedAddress {
var url:NSURL? = NSURL(string: escapedAddress)
if let url = url {
cell.imageOutlet.contentMode = UIViewContentMode.ScaleAspectFill
cell.imageOutlet.hnk_setImageFromURL(url, placeholder: UIImage(named: "placeholderImage.png"), format: nil, failure: nil, success: nil)
}
}
}
}
}
}
return cell
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if self.allImagesSlideshow[collectionView.tag] != nil {
var arr:[String]? = self.allImagesSlideshow[collectionView.tag]!
if let arr = arr {
println("collection row: \(collectionView.tag), items:\(arr.count)")
return arr.count
}
}
return 0
}
I set the right image for the cell. The problem is that the above method is called only for the first collection view cell. So when the user swipe to the next collection view cell the above method is called again but and there is a delay while the image is downloaded. I would like all the collection view cells to be loaded insight every visible table view cell, not only the first one.
Using the image I have posted, "Collection View Cell (number 0)" is loaded every time but "Collection View Cell (number 1)" is loaded only when the user swipe to it. How I can force calling the above method for every cell of the collection view, not only for the visible one? I would like to start the downloading process before swiping of the user.
Thank you!
you're right. the function func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell will be called only when cell start to appear. that's a solution of apple called "Lazy loading". imagine your table / collection view have thousand of row, and all of those init at the same time, that's very terrible with both memory and processor. so apple decide to init only view need to be displayed.
and for loading image, you can use some asynchronous loader like
https://github.com/rs/SDWebImage
it's powerful and useful too :D