UICollectionView Dynamic Cell Height as per the content | Pinterest Layouts - swift

I am trying to do a layout as Pinterest uses for their image gallery. But the issue is that I'm returning the image height to the size of the cells and when the images have different heights, it leaves spaces in the cells.
I have walked through lots of articles, but I have not found a simple solution. Is there any simple way we can do this?
Please see this image
class ViewController: UIViewController {
#IBOutlet var collectionVIew: UICollectionView!
var imgData = [#imageLiteral(resourceName: "p1"),#imageLiteral(resourceName: "p2"),#imageLiteral(resourceName: "p3"),#imageLiteral(resourceName: "p4"),#imageLiteral(resourceName: "p5"),#imageLiteral(resourceName: "p6"),#imageLiteral(resourceName: "p7"),#imageLiteral(resourceName: "p8"),#imageLiteral(resourceName: "p1"),#imageLiteral(resourceName: "p2"),#imageLiteral(resourceName: "p3"),#imageLiteral(resourceName: "p4"),#imageLiteral(resourceName: "p5"),#imageLiteral(resourceName: "p6"),#imageLiteral(resourceName: "p7"),#imageLiteral(resourceName: "p8"),#imageLiteral(resourceName: "p1"),#imageLiteral(resourceName: "p2"),#imageLiteral(resourceName: "p3"),#imageLiteral(resourceName: "p4"),#imageLiteral(resourceName: "p5"),#imageLiteral(resourceName: "p6"),#imageLiteral(resourceName: "p7"),#imageLiteral(resourceName: "p8"),#imageLiteral(resourceName: "p1"),#imageLiteral(resourceName: "p2"),#imageLiteral(resourceName: "p3"),#imageLiteral(resourceName: "p4"),#imageLiteral(resourceName: "p5"),#imageLiteral(resourceName: "p6"),#imageLiteral(resourceName: "p7"),#imageLiteral(resourceName: "p8")]
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.size.width
let img = imgData[indexPath.item]
// 335 is the width of my cell image
return CGSize(width: width/2-15, height: (img.size.height / 335) * width)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imgData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.imgView.image = imgData[indexPath.row];
cell.imgView.contentMode = .scaleAspectFit
return cell
}
}

What you have mentioned can be achieved using flow layout — a layout class provided by UIKit.
This is what you are looking for:
https://www.raywenderlich.com/392-uicollectionview-custom-layout-tutorial-pinterest
I hope you can put some effort into reading this very simple post and follow each step carefully.

You are trying to fit non-square images into a square hole. When we do this something has to be given. In this case, I would recommend you make each of your cells a fixed height and set an aspect fill content mode on them so they fill the cell's frame completely. The downside is that some of the larger images will have part of them cut off. The other option is to use the aspect fit content mode which will scale the image to fit without losing a part of the image. This would leave you with the cell spacing again.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.size.width
return CGSize(width: width/2-15, height: 100) // fixed height
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.imgView.image = imgData[indexPath.row];
cell.imgView.contentMode = .scaleAspectFill // Set the scale mode to aspect fill
return cell
}
Alternatively, you can check out some open source libraries that might implement this for you. This one looks promising https://github.com/abdullahselek/ASCollectionView
You can check out a larger list of collection view open-source libraries here https://github.com/vsouza/awesome-ios#collection-view
If you want the cells to have different heights you need to calculate the correct height based on the aspect ratio of the image and the width that is fixed. You calculate the aspect ratio in this case by the height/width when our width is fixed and our height is dynamic. Then multiply that by the width of the cell of the image to get our dynamic cell height.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.size.width / 2 - 15;
let img = imgData[indexPath.item]
return CGSize(width: width, height: (img.size.height / img.size.width) * width)
}
You should set your content mode to aspect fit in this case.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.imgView.image = imgData[indexPath.row];
cell.imgView.contentMode = .scaleAspectFit // Set the scale mode to aspect fit
return cell
}

Related

How Can I align the colectionViewCells to the left?

I have a cUIolectionView that sometimes has only 1 item. If that is the case, the item is aligned in the middle, see picture:
But what I want, is that the item is aligned to the left.
I have found this answer on StackOverflow, leftAlign cells in UIColectioniew - StackOverFlow, but when I added the class given in the accepted answer to my codeBase, and added this to the viewControler:
let leftAlignLayout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, verticalAlignment: .top)
gamesColectionView.collectionViewLayout = leftAlignLayout
it did align the way I wanted, but it made the cells very small(see picture)
I also tried to add this from Github: AlignedCollectionViewFlowLayout - GitHub but that had the same result.
I tried fixing that bug by adding this to my viewController File:
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 130, height: 130)
}
but that didn't work.
does anybody have a suggestion on how I can fix this? I don't know what I did wrong or what I forgot to do.
thanks a lot!
BSM
You can manage your cell with below code. Do not forget to add UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
// your code here
}
Try this:
var layout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 8.0
layout.minimumInteritemSpacing = 8.0
let width = (UIScreen.main.bounds.size.width - 40.0) / 4
layout.estimatedItemSize = CGSize(width: width, height: 98.0)
return layout
}()
And:
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView?.collectionViewLayout = layout
self.collectionView!.contentInset = UIEdgeInsets(top: 0, left: 0, bottom:0, right: 0)
}
You don't need to use sizeForItemAt. If you want more you can look my project. Check constraints of your imageView with it's superview
You don't need to do anything special to get the collectionViewCells left aligned.
If the constraints are proper, the cells will automatically start rendering from the left.
class VC: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 130, height: 130)
}
}
Simply set the scrollDirection to vertical or horizontal as per your requirement within the storyboard itself.
There is no need to use any 3rd party library to get that working.

How to modify gridView with the following example

I've been trying to figure out how to accomplish this kind of gridView display for my app,
Image example of what i need to accomplish
i have this block of codes, which came from another question relating to gridView,
but the texts inside each views are not displaying properly
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let flowayout = collectionViewLayout as? UICollectionViewFlowLayout
let space: CGFloat = (flowayout?.minimumInteritemSpacing ?? 0.0) + (flowayout?.sectionInset.left ?? 0.0) + (flowayout?.sectionInset.right ?? 0.0)
let width = self.collectionView.frame.size.width
let size:CGFloat = (width - space) / 2.0
if indexPath.row == 0 {
return CGSize(width: width, height: width / 2.0)
}
return CGSize(width: size, height: size)
}
Well , This is something you have just give layout to collection view but you have to use CustomCell to proper design inside items of collectionview, also for above layout guide i would suggest to use Extension like this
Extension Yourviewcontroller: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = self.collectionView.frame.size.width
let size:CGFloat = (width - space) / 2.0
if indexPath.row == 0 {
return CGSize(width: width, height: width / 2.0)
}
return CGSize(width: size, height: size)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
}
Regarding custom cell creation use this link https://www.ioscreator.com/tutorials/custom-collection-view-cell-ios-tutorial ,
In your case just use one cell with one image and 2 lables below and make them centerAlign(Using constraint) And use that cell on your viewcontroller as below
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
cell.imageView.image = Yourimage
cell.lableName.text = yourlable
cell.lableDetail.text = yourdetailtext
return cell
}
let me know if you need any other help on this

How can I programmatically insert UICollectionView into UITableViewCell ? [Swift]

Does anyone know how to integrate a collectionView into the top section of a tableView? I'm trying to get a top section/row that has horizontally scrolling images, and other section/rows below it to just be normal tableview rows. Like what you might see in an app like facebook messenger or tinder/bumble's matches/conversations page.
I understand how to produce UICollectionView and UITableView separately, but between the multiple functions/delegates/datasources, I can't figure out how to properly layer the code.
I am working programmatically rather than on storyboard. I know there are other posts on inserting UICollectionView in UITableViewCell, but they're either storyboard implementations, or on outdated versions of Swift - would be awesome if someone could do a run-through so that anyone looking in the future could also understand the logic even if some of the code becomes outdated.
declare collectionView in the TableViewCell swift file.
var viewPhotos: UICollectionView!
func reset(with cellData: CellData) {
self.data = cellData
viewPhotos.dataSource = self
viewPhotos.delegate = self
viewPhotos.reloadData()
}
in the init function or awakeFromNib check if photoCollectionView is initialized and add it to the cell.
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
viewPhotos = UICollectionView(frame: self.bounds, collectionViewLayout: layout)
viewPhotos.collectionViewLayout = layout
viewPhotos.showsHorizontalScrollIndicator = false
viewPhotos.isPagingEnabled = true
viewPhotos.register(PhotoCollectionViewCell.self, forCellWithReuseIdentifier: PhotoCollectionViewCell.cellIdentifier)
Add UICollectionView Datasource and Delegate To the UITableViewCell
extension TableViewCell: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,numberOfItemsInSection > section: Int) -> Int {
return photoUrls?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: PhotoCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoCollectionViewCell.cellIdentifier, for: indexPath) as! PhotoCollectionViewCell
if let value = photoUrls {
cell.reset(with url: value[indexPath.row]);
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: kPhotoWidth, height: kPhotoHeight)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, layout > collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
}
PhotoCollectionViewCell
create a PhotoCollectionViewCell derived from UICollectionViewCell
and add a UIImageView, and make all edge constraints to 0 to the
superview. load UIImageView based on photoUrl.

Different spacing between cells with UICollectionView and FlowLayout

I have a problem with dynamic cell sizing in a UICollectionView, or maybe a logic problem. I would like to calculate the width of the cells in sizeForItemAt indexPath: IndexPath. I have a minimum spacing of 1. The problem is that the board/spacing between my three cells is not the same as you can see in the picture. The spacing between the first and second cell looks bigger than the spacing between the second and the third cell.
My code:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 9
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCell
cell.backgroundColor = UIColor.gray
cell.name.text = "My Textfield"
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = mycollectionView.frame.width
let cellsInLine: CGFloat = 3
let widthWithoutSpacing = width - (spacing * (cellsInLine - 1))
let cell = CGSize(width: widthWithoutSpacing / cellsInLine, height: 50)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return spacing
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return spacing
}

How do I change the size of a 2x2 UICollectionView and fit the whole screen: Swift 3 Xcode 8

Hi I am creating an app which requires a grid of images. As an example I am using a 2x2 grid of images in cells of a UICollectionView. I would like to know how I could decrease the annoying space in the middle and also make them have equal widths and heights to fit the whole screen. I know that I can do this with Interface builder. However, I want to do this programmatically because as the app progresses, there will be different levels and different number of squares - possibly a 10x10 also. I am new to UICollectionViews. I have referenced to many other questions, however most are in Objective C and there few that are in Swift don't seem to work with Swift 3.
Here is my full code:
import UIKit
class GameViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout{
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
collectionView.delegate = self
collectionView.dataSource = self
let collectionViewLayout = self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout
collectionViewLayout.minimumLineSpacing = 0
collectionViewLayout.minimumInteritemSpacing = 0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2;
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
let image = cell.viewWithTag(1) as! UIImageView
image.image = #imageLiteral(resourceName: "coffee")
return cell;
}
func collectionView(_collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSize(width: self.collectionView.frame.size.width/2, height: self.collectionView.frame.size.height/2)
//this method is not getting called
}
}
I am also uploading an image of how it currently is and how I want it to be-
I hope you guys will help me- it will be very useful... Thanks!
I conformed to UICollectionViewDelegateFlowLayout and called sizeforitematindexpath but it did not work.
On adding a breakpoint I realized that the method was not getting called. So is there anything wrong I'm doing here. Also Xcode gives me this warning -
Make sure that:
Confirming to UICollectionViewDelegateFlowLayout.
collectionView's width = screen's width.
Min Spacing for cells is zero. You can change it from the Interface Builder (Select the collectionView -> show size inspector -> set min spacing to 0), or by implementing minimumInteritemSpacingForSectionAt returning zero.
If you want to fill the whole Screen height (I am asking because this is not what provided in your screenshot), collectionView's height = screen's height.
Finally, implement sizeForItemAtIndexPath method:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width / 2, height: 100)
}
Use following code in viewDidLoad
let collectionViewLayout = self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout
collectionViewLayout.minimumLineSpacing = 0
collectionViewLayout.minimumInteritemSpacing = 0
collectionViewLayout.itemSize = CGSize(width: UIScreen.main.bounds.width/2, height: UIScreen.main.bounds.width/2)
Use "UICollectionViewDelegateFlowLayout" Delegate for set size for CollectionView cell size
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width/2, height: UIScreen.main.bounds.width/2)
}
I hope this will helpfull for you.
You can use the following to size the cells:
extension GameViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellWidth = self.collectionView.bounds.width / 2
return CGSize(width: cellWidth, height: cellWidth)
}
}
Also, add the following lines at the end of viewDidLoad() to get rid of the unwanted white space:
let collectionViewLayout = self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout
collectionViewLayout.minimumLineSpacing = 0
collectionViewLayout.minimumInteritemSpacing = 0