How to implement UICollectionViewDelegateFlowlayout's sizeForItemAtIndexPath with Async images? - ios

I'm trying to implement UICollectionViewDelegateFlowlayout's sizeForItemAtIndexPath similar to this tutorial: http://www.raywenderlich.com/78550/beginning-ios-collection-views-swift-part-1
The problem I have is that my photos are downloaded via an async call on the web, so when sizeForItemAtItemPath is called, my images haven't loaded yet. In other words, the code below crashes on imageArray. How can I delay the method until my images are loaded? I've also tried using a hardcoded with and height value if no images are available, but then I only return those hardcoded values, i.e. sizeForItemAtIdexPath doesn't seem to get called again after the images are loaded.
func pinImageForIndexPath(indexPath: NSIndexPath) -> UIImage {
return imageArray[indexPath.row]
}
}
extension PinCollectionViewController : UICollectionViewDelegateFlowLayout {
//1
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let pinImage = pinImageForIndexPath(indexPath)
//2
if var size = pinImage.size as? CGSize {
size.width += 10
size.height += 10
return size
}
return CGSize(width: 100, height: 100)
}
//3
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAtIndex section: Int) -> UIEdgeInsets {
return sectionInsets
}
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! PinCollectionViewCell
cell.pinImage?.pin_updateWithProgress = true
cell.pinImage?.image = nil
var actInd: UIActivityIndicatorView = UIActivityIndicatorView()
actInd.frame = cell.pinImage.bounds
actInd.hidesWhenStopped = true
actInd.activityIndicatorViewStyle =
UIActivityIndicatorViewStyle.Gray
cell.addSubview(actInd)
actInd.startAnimating()
if let pinImageURL = self.pins[indexPath.row].largestImage().url {
cell.pinImage?.pin_setImageFromURL(pinImageURL, completion: ({ (result : PINRemoteImageManagerResult) -> Void in
actInd.stopAnimating()
if let image = result.image {
self.imageArray.append(image)
}
self.collectionView?.reloadItemsAtIndexPaths([indexPath])
}))
}
return cell
}

Indeed, you should return some hardcoded values until you haven't loaded image. Strictly saying, you should display some predefined placeholder for not yet loaded image.
When your image get loaded (when async download operation successfully completes), you should call -reloadItemsAtIndexPaths: on collection view with index path's of those cells, images for which are available.

Related

Call collectionView(_ :layout:sizeForItemAt:) in Swift 4

I made the gallery using UICollectionView.
If you lower the screen, the image should continue to come out and stop at 20.
The code was constantly updated.
However, the function collectionView (_: layout: sizeForItemAt :) that sets the size of the cell did not work.
Therefore, only 20 images are displayed continuously.
On viewDidLoad()
if let layoutt = artCollectionView?.collectionViewLayout as? PinterestLayout
{
layoutt.delegate = self
}
cell setting
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HomeCollectionViewCell", for: indexPath) as? HomeCollectionViewCell,
let arts = self.artList else { return HomeCollectionViewCell() }
if arts.count > indexPath.row
{
let model = arts[indexPath.row]
cell.imgView.sd_setImage(with: URLHelper.createEncodedURL(url: model.thumburl), completed: nil)
}
return cell
}
return cell size
extension HomeViewController : PinterestLayoutDelegate
{
func collectionView(_ collectionView: UICollectionView, heightForPhotoAtIndexPath indexPath:IndexPath) -> CGFloat
{
return CGFloat(300.0)
}
}
Currently, the function only executes once when the app starts. Do you know how to run that function every time I want?
You need to implement UICollectionViewDelegateFlowLayout.
You can read more about the protocol here:
UICollectionViewDelegateFlowLayout protocol
Example:
extension viewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 50, height: 300)
}
}
If you look at the prepare() method of PinterestLayout class, you can see the below code
guard
cache.isEmpty == true,
let collectionView = collectionView
else {
return
}
When you reload the colectionView the cache is no longer empty, so it returns back without altering the layout.
So just remove the cache value when prepare is called.
Add removeAll() before guard
cache.removeAll()
Please follow the steps to install PinterestLayout in UICollectionView below.
Set PinterestLayout to your collection view.
let layout = PinterestLayout()
collectionView.collectionViewLayout = layout
Setup layout
layout.delegate = self
layout.cellPadding = 5
layout.numberOfColumns = 2
Implement methods of PinterestLayoutDelegate
extension CustomCollectionVC: PinterestLayoutDelegate {
func collectionView(collectionView: UICollectionView,
heightForImageAtIndexPath indexPath: IndexPath,
withWidth: CGFloat) -> CGFloat {
let image = images[indexPath.item]
return image.height(forWidth: withWidth)
}
func collectionView(collectionView: UICollectionView,
heightForAnnotationAtIndexPath indexPath: IndexPath,
withWidth: CGFloat) -> CGFloat {
let textFont = UIFont(name: "Arial-ItalicMT", size: 11)!
return "Some text".heightForWidth(width: withWidth, font: textFont)
}
}
When data is loaded invalidate layout as well as reload data on collection view.
collectionView.collectionViewLayout.invalidateLayout()
collectionView.reloadData()
Observation:
func collectionView(_ collectionView: UICollectionView, heightForPhotoAtIndexPath indexPath:IndexPath) -> CGFloat
try this instead of above:
func collectionView(collectionView: UICollectionView,
heightForImageAtIndexPath indexPath: IndexPath,
withWidth: CGFloat) -> CGFloat

UICollectionView with array of images set it right

I have a question. Inside UICollectionView I have UIImage with Content Mode - Aspect Fit... Now I need that in UICollectionView images show one near another. Now I have a lot of space between them.
extension UserDetailInformationOfAnimalViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
//MARK: - Collections
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let post = postsArray[collectionView.tag]
// print("count: ", post.imagesLink.count)
return post.imageLinks.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
let post = postsArray[collectionView.tag]
// print("postArrayPhotos: ", post.imagesLink.count)
if post.imageLinks.count != 0 {
// print("POST = ", post)
// print("Look here: ", post.imagesLink[indexPath.row].imageLink)
// print("IMAGELINK = ", post.imagesLink[indexPath.row].imageLink)
let imageLink = post.imageLinks[indexPath.row]
if imageLink.imageLink != nil {
let url = URL(string: imageLink.imageLink!)
print("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
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height = self.view.frame.size.height;
let width = self.view.frame.size.width;
// in case you you want the cell to be 40% of your controllers view
return CGSize(width: width, height: height * 0.35)
}
}
Use this method to set width of cell dynamically
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if indexPath.row == whatever you want {
return CGSize(width: collectionView.frame.size.width/2, height: collectionView.frame.size.width/2)
}
}
I understand your problem. Here I give you a way around. This solution may not effective for you but it will give you idea how to solve it. Main concept is the you have to return cell size according to image. But instantly image is not in your hand. Its on the server. So you can do that fist return a demo or place holder image height then after finishing download actual image from server tell the collection view to re-layout the cell according to actual image size.
cell.imageOfAnimalInCollectionView.sd_setImage(with: url!, placeholderImage: UIImage(named: "App-Default"),options: SDWebImageOptions(rawValue: 0), completed: { (image, error, cacheType, imageURL) in
//told the collection view to relayout according to image size
collectionView.collectionViewLayout.invalidateLayout()
})
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cell = collectionView.cellForItem(at: indexPath) as! CollectionViewCell
return cell.imageOfAnimalInCollectionView.image.size! // Put your logic here, until actual image download from server you can return a default size.
}

UICollectionView overlapping labels

I am using a collectionview and the first data load is working fine. The problem occurs when the data is load a second time the labels overlaps because the old labels appear to still exist.
Below is my collectionview code, thanks in advance.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let customCell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCellIdentifier", for: indexPath)
customCell.layer.borderColor = UIColor.black.cgColor
customCell.layer.borderWidth = 1
// changed these lines here
if cellsLabels[indexPath.row] != nil {
customCell.willRemoveSubview(cellsLabels[indexPath.row]!)
}
//to these lines here and the problem was solved
let maybe = customCell.subviews
for i in 0 ..< maybe.count {
maybe[i].removeFromSuperview()
}
let c
ommentLabel = UILabel()
commentLabel.text = commentsArray[indexPath.row]
commentLabel.frame = CGRect(x: 0, y: 50, width: 200, height: 30)
customCell.addSubview(commentLabel)
self.cellsLabels[indexPath.row] = commentLabel
if indexPath.row == commentLoadTracker*10 - 1 {
print("working doomfist")
}
return customCell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return commentsArray.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var cellSize = CGSize()
cellSize.width = self.commentView.frame.width
cellSize.height = 100
return cellSize
}
customCell.willRemoveSubview
You are calling willRemoveSubiew instead of removeFromSuperview
if cellsLabels[indexPath.row] != nil {
cellsLabels[indexPath.row]!.removeFromSuperview()
}
There is no need to call willRemoveSubview, UIKit calls it for you, it only exists so that it can be
Overridden by subclasses to perform additional actions before subviews are removed from the view.

Animating a CollectionViewCell Size To Outside CollectionView Boundary - Swift

I have been working on animating the size of some collectionViewCells. It is working great in my collectionViewController, but in another UIViewController where I have a small collectionView alongside other objects, the cell will only grow to the size of the collectionView. I would like it to fill the whole screen. What is the best way about doing this?
Here is my current code:
var largePhotoIndexPath : NSIndexPath? {
didSet{
var indexPaths = [NSIndexPath]()
if largePhotoIndexPath != nil {
indexPaths.append(largePhotoIndexPath!)
}
if oldValue != nil {
indexPaths.append(oldValue!)
}
collectionView?.performBatchUpdates({ () -> Void in
self.collectionView?.reloadItemsAtIndexPaths(indexPaths)
return
}){
completed in
if self.largePhotoIndexPath != nil {
self.collectionView?.scrollToItemAtIndexPath(self.largePhotoIndexPath!, atScrollPosition: .CenteredVertically, animated: true)
}
}
}
}
func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
if largePhotoIndexPath == indexPath {
largePhotoIndexPath = nil
}
else {
largePhotoIndexPath = indexPath
}
return false
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let dealImage: UIImage = UIImage(data: self.imageDataArray[indexPath.row])!
let dealPhoto: UIImageView = UIImageView(image: dealImage)
if indexPath == largePhotoIndexPath {
var size = UIScreen.mainScreen().bounds
dealPhoto.frame == size
return CGSize(width: 300, height: 525)
}
return CGSize(width: 100, height: 175)
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.imageDataArray.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! TrailInfoCollectionViewCell
cell.imageView.image = UIImage(data: self.imageDataArray[indexPath.row])
return cell
}
And some images before and after I select the cell:
As you can see, the image has grown to the size I want it, but it is constrained by the collectionView. Is it possible for a collectionViewCell to exceed the boundaries of a collectionView, or will I have to do it some other way? I would just set up a hidden imageView but I like the animated transition.
Thanks for your help. Please let me know if you need any more details.
clipsToBounds properties is what prevents a subview from growing past it's parent. Set it to false and it should work.

How to make UICollectionView auto layout

How to make UICollectionView auto layout such that the number of items appearing on the screen at some moment is decided according to total number of items. I actually want to make such layout that if i have 9 items than all nine items should be displayed at the same time on the collection view. if items are 8 or something else then the size of the items should increase in such a manner that they still consume complete area of the collection view.
the condition i have plotted is as under:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var cell = collectionView.cellForItemAtIndexPath(indexPath) as CollectionViewCell
if((cell.selected) && (indexPath.row%2 == 0))
{
cell.backgroundColor = UIColor.whiteColor()
imageview.image = UIImage( named: a[indexPath.row])
}
else if((cell.selected) && (indexPath.row%2 != 0))
{
cell.backgroundColor = UIColor.whiteColor()
imageview.image = UIImage( named: a[indexPath.row])
}
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var abc : CGSize!
if(a.count == 1){
abc = CGSize(width: self.view.frame.width, height: self.view.frame.height/2)
}
else if(a.count == 2) {
abc = CGSize(width: self.view.frame.width/2, height: self.view.frame.height/2)
}
else if (a.count > 2) && (a.count<6){
abc = CGSize(width: self.view.frame.width/3, height: self.view.frame.height/2)
}
else {
abc = CGSize(width: self.view.frame.width/3, height: self.view.frame.height/5)
}
return abc
}
func collectionView(collectionView: UICollectionView, shouldHighlightItemAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool
{
return true
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return a.count
}
func collectionView(collectionView: UICollectionView, shouldShowMenuForItemAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
From your question I understand that you want to very the UICollectionView's cell sizes so they cover the whole screen.
There are various ways you could achieve what you want, but the key will be to work with the UICollectionViewLayout.
For example, the UICollectionViewFlowLayout has an itemSize property.
Based on your content (for the cells), you could try calculating how much space they will occupy and then set the itemSize property of the FlowLayout. If all the cells are supposed to be the same size, this should be good enough.
You could also use the delegate method collectionView:layout:sizeForItemAtIndexPath: to set the size of each cell.
If that's not sufficient, because you want to change the arrangement of the cells as well for example, you will need to subclass the FlowLayout in order to calculate the size for each item. Have a look at this:
https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html
Ultimately, you need to start by calculating how many cells you'll need based on your content first.

Resources