I am trying to have 2 collectionView cells per row in my CollectionView, I want them to both be half of the width of the collectionView, but when doing that it only displays 1 cell per row, this is the code I have to size it:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.collectionView.frame.width / 2, height: 190)
}
I'm not sure why this shouldn't work, can anyone help out?
When I subtract some width it displays 2 per row, but then the cells' width are smaller than half of the screen size
You need to set minimumLineSpacing to 0
layout.minimumLineSpacing = 0
Or
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
Had to set spacing to 0 in the UICollectionView attribute inspector
Sometime you must use both via collectionViewDelegateFlowLayout and second like below:
func setupCollectionView() {
let layout = UICollectionViewFlowLayout()
layout.sectionInset = .zero
layout.minimumLineSpacing = 0 //as per your requirement
layout.minimumInteritemSpacing = 0 //as per your requirement
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: self.yourCollectionView.frame.width / 2, height: 190)
self.yourCollectionView.collectionViewLayout = layout
}
Related
So I have UICollectionView on my Controller
UICollection view consist of two sections.
So when I start scrolling, it changes cell sizes
My code for layout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let section = indexPath.section
let row = indexPath.row
if section == 0 {
if (row == 0){
let width = view.frame.width
return.init(width: width, height: 120)
}else{
let width = view.frame.width
return.init(width: width, height: 50)
}
}else{
//
//return.init(width: 200, height: 120)
let width = collectionView.bounds.width / 2 - 10
return.init(width: width, height: 253)
}
}
Producs are in second section, but it looks like it gives a width from first section
Make sure estimateSize is set to NONE in storyboard.
Your error the first size for section 0 but anther section you changeded the cell size you can use this code :
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.bounds.width / 2 - 10
let height = collectionView.bounds.height
return.init(width: width, height: height)
}
I read
How to vertically center UICollectionView content
But when I used the codes here
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let navBarHeight = self.navigationController?.navigationBar.frame.height
let collectionViewHeight = (self.collectionView?.frame.height)! - navBarHeight!
let itemsHeight = self.collectionView?.contentSize.height
let topInset = ( collectionViewHeight - itemsHeight! ) / 4
return UIEdgeInsetsMake(topInset ,0, 0 , 0)
}
But when scrolling collection view this will show incorrect form
so here is my codes because my collection view cells are square and equal to screen width
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
showImagesCV.contentInset = UIEdgeInsets(top: spacing, left: spacing, bottom: spacing, right: spacing)
if let flowLayout = showImagesCV.collectionViewLayout as? UICollectionViewFlowLayout{
cellWidth = UIScreen.main.bounds.width
cellHeight = cellWidth //yourCellHeight = cellWidth if u want square cell
flowLayout.minimumLineSpacing = spacing
flowLayout.minimumInteritemSpacing = spacing
UIView.performWithoutAnimation {
}
}
}
}
extension showImagesViewController : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: cellWidth, height: cellHeight)
}
}
I want the spacing between cells be 0 and each cell that is showing (current page (because the collection view is in paging mode)) be the center of the screen
Regarding the SO Question you mentioned and using, that is to centre the whole section not individual cells. Some of your code might be invalid(needs to be redone).
First and most important, set the constraints of your collectionView for full screen. Remember to take safeAre/TopGuide into account, this will make sure that collection view is below the navigation bar if there is one.
This will make sure that your collectionView is up to date
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
myCollectionView.collectionViewLayout.invalidateLayout()
}
Dump/comment below code and set insets to (0,0,0,0) in inspector of collectionView in story board. In same inspector change Min Spacing to 0 for cells and for line, both.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let navBarHeight = self.navigationController?.navigationBar.frame.height
let collectionViewHeight = (self.collectionView?.frame.height)! - navBarHeight!
let itemsHeight = self.collectionView?.contentSize.height
let topInset = ( collectionViewHeight - itemsHeight! ) / 4
return UIEdgeInsetsMake(topInset ,0, 0 , 0)
}
Also remove/comment below code
showImagesCV.contentInset = UIEdgeInsets(top: spacing, left: spacing, bottom: spacing, right: spacing)
if let flowLayout = showImagesCV.collectionViewLayout as? UICollectionViewFlowLayout{
cellWidth = UIScreen.main.bounds.width
cellHeight = cellWidth //yourCellHeight = cellWidth if u want square cell
flowLayout.minimumLineSpacing = spacing
flowLayout.minimumInteritemSpacing = spacing
UIView.performWithoutAnimation {
}
}
Now change your sizeForItemAt like this.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return collectionView.frame.size
}
UICollectionViewCell
Now in your collectionView cell, make sure that your image view is 1:1 and in centre. Also handle constraints of your other UI components.
PS : This is easy approach, other would be to write custom flowLayout
I am using the UICollectionViewDelegateFlowLayout in my subclassed UICollectionViewController, I am setting the heights of the cells with this:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellHeight = arc4random_uniform(100) + 200
print("cell height: \(cellHeight)")
return CGSize(width: collectionView.bounds.size.width/2 - 1, height: CGFloat(cellHeight))
}
And the cells now look like this:
Is there a way I can make the CollectionViewCells have 0 vertical space in between cells? Obviously this would mean the rows are not aligned, so does that make it impossible to do with a Flow layout?
Use this in viewDidLoad:
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
So as the title suggest, I seem to have run into a weird problem. All I am trying to do here is create a 2 column collectionview without hardcoding anything into my delegate methods. Upon debugging I found that insetForSectionAt is called after sizeForItemAt, hence the custom insets are not taken into consideration when calculating the size of every cell.
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
print("left: ", flowLayout.sectionInset.left) // Prints out 0.0
print("right: ", flowLayout.sectionInset.right) // Prints out 0.0
let marginsAndInsets = flowLayout.sectionInset.left + flowLayout.sectionInset.right + flowLayout.minimumInteritemSpacing * (cellsPerRow - 1)
let itemWidth = (collectionView.bounds.size.width - marginsAndInsets) / cellsPerRow
return CGSize(width: itemWidth, height: itemWidth)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
}
}
This problem can be solved very uglily by hard coding the values directly into the itemWidth variable as such, -20 because i know the values of left and right insets, 10 + 10
let itemWidth = (collectionView.bounds.size.width - marginsAndInsets - 20) / cellsPerRow
however, I have to believe that there is a better way of doing this, how can I call reloadData upon completion of the UIEdgeInset calculation so that my cells are properly sized?
So this is what I ended up doing, i just added the customizations directly when creating the sizes for the collectionView and its cells.
class PinController: UICollectionViewController {
fileprivate let pinCellId = "pinCellId"
fileprivate let cellsPerRow: CGFloat = 2.0
fileprivate let margin: CGFloat = 10.0
fileprivate let topMargin: CGFloat = 2.0
}
extension PinController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let flowLayout = customize(collectionViewLayout, margin: margin)
let itemWidth = cellWidth(collectionView, layout: flowLayout, cellsPerRow: cellsPerRow)
return CGSize(width: itemWidth, height: itemWidth * 1.1)
}
/*
Customizes the layout of a collectionView with space to the left and right of each cell that is
specified with the parameter margin
*/
private func customize(_ collectionViewLayout: UICollectionViewLayout, margin: CGFloat) -> UICollectionViewFlowLayout {
let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
// Interitem spacing affects the horrizontal spacing between cells
flowLayout.minimumInteritemSpacing = margin
// Line spacing affects the vertical space between cells
flowLayout.minimumLineSpacing = margin
// Section insets affect the entire container for the collectionView cells
flowLayout.sectionInset = UIEdgeInsets(top: topMargin, left: margin, bottom: 0, right: margin)
return flowLayout
}
/*
Calculates the proper size of an individual cell with the specified number of cells in a row desired,
along with the layout of the collectionView
*/
private func cellWidth(_ collectionView: UICollectionView, layout flowLayout: UICollectionViewFlowLayout, cellsPerRow: CGFloat) -> CGFloat {
// Calculate the ammount of "horizontal space" that will be needed in a row
let marginsAndInsets = flowLayout.sectionInset.left + flowLayout.sectionInset.right + flowLayout.minimumInteritemSpacing * (cellsPerRow - 1)
let itemWidth = (collectionView.bounds.size.width - marginsAndInsets) / cellsPerRow
return itemWidth
}
}
I've been looking for how to make UICollectionView have a fixed spacing between its cells, but have not found a solution so far.
I have a UICollectionView which consists of two columns, and all the cells have the same width, but they vary in height. I have only one section in this UICollectionView.
I have set the minimum spacing to 10(which is the value I want between cells vertically), but the system is seemingly randomly stretching the vertical space.
What I would like to achieve, is the effect Wikipedia for iPad has: different article tiles with the same width, but adjustable height according to need, yet all the cells are spaces evenly vertically. (I don't mind leaving space at the very end, but I don't want extra spaces in the middle)
How should I achieve this? I would accept third-party libraries if it is easy to migrate existing UICollectionViews(and it's open-source, of course).
In swift 3,
you can implement this delegate method for cell spacing in collectionView :
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
// Give cell width and height
return CGSize(width: 750, height: 320)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat{
// splace between two cell horizonatally
return 50
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat{
// splace between two cell vertically
return 100
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets{
// give space top left bottom and right for cells
return UIEdgeInsets(top: 0, left: 100, bottom: 100, right: 100)
}
All right, so the reason after all is that the spacing that I've set was the MINIMUM spacing between cells. The collectionView could use whatever spacing needed to align cells on the same row, as long as it's not less than the minimum.
If you want to have fixed inter item spacing on UICollectionView you can use the custom layout below. Note: I've created this layout for fixed height cells and .vertical scroll direction.
class CollectionViewFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let layoutAttributes = super.layoutAttributesForElements(in: rect) else {
return nil
}
var startX: CGFloat = 0.0
var startY: CGFloat = 0.0
let contentWidth = collectionView?.frame.width ?? 0
for layoutAttributes in layoutAttributes {
let size = layoutAttributes.size
if (startX + size.width + minimumInteritemSpacing) > contentWidth {
startX = 0
startY += size.height
startY += minimumLineSpacing
}
let origin = CGPoint(x: startX, y: startY)
let frame = CGRect(origin: origin, size: size)
layoutAttributes.frame = frame
startX += size.width + minimumInteritemSpacing
}
return layoutAttributes
}
}