I now have a collectionView with 4 cells per row.
I've set layout like the following
let width = UIScreen.main.bounds.width / 4
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: width, height: width)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
self.module_CollectionView.collectionViewLayout = layout
which should give me a collectionView without any spacing between cells.
However, I still got very thin (looks like 1px) space between columns.
(please refer to the screen shot below)
It works fine if the cell width is 1/5 or 1/3 of Screen width.
I guess it's because the remainder or something ?
Would be good if someone could point out what might goes wrong.
thanks.
I think you might set wrong constraint, otherwise nothing is wrong in your code for flowlayout.
Now, first take one UIView in your collectionviewcell and set it's four constraint - top.leading,trailing,bottom with constant 0.
Then add your all the stuff (your views or images or buttons) in that view and give proper constraint.
You need to implement collectionView(_ collectionView: UICollectionView, layout collectionViewLayout:UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize.
In your case, the screen width is 375, and you want to divides it to 4 parts. If you directly divides 375 to 4 parts, you'll get 93.75, with decimal.
You should round it(decimal) to an integer like 94 + 94 + 94 + 93 = 375 instead of 93.75+93.75+93.75+93.75 = 375.
For Example:
override func viewDidLoad() {
super.viewDidLoad()
let screenWidth:Int = UIScreen.main.bounds.width
let parts:CGFloat = 4
var rounding:Int = 0
if(screenWidth.truncatingRemainder(dividingBy: parts) != 0) {
rounding = Int(screenWidth.truncatingRemainder(dividingBy: parts))
}
}
//----
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var roundingWidth = 0
if(rounding != 0) {
rounding -= 1
roundingWidth = 1
}
return CGSize(width: Int(screenWidth / parts) + roundingWidth, height: yourHeight)
}
Related
This is what I have:
A Collection View with 2 columns with each an equal distance apart.
Each cell loads SmallCardView.xib. The SmallCardView contains a square image with some text below.
The problem:
I want the width of the view to match that of it's parent (the cell). This is best illustrated by comparing screen sizes
As you can see above, the cell (purple outline) sizes correctly on both screens but the SmallCardView remains the same size
Here is the code in my Collection View Controller:
viewDidLoad -
private let spacing: CGFloat = 20.0
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: spacing, left: spacing, bottom: spacing, right: spacing)
self.collectionView?.collectionViewLayout = layout
}
sizeForItemAt -
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let numberOfItemsPerRow: CGFloat = 2
let spacingBetweenCells: CGFloat = 20
let totalSpacing = (2 * self.spacing) + ((numberOfItemsPerRow - 1) * spacingBetweenCells) // Amount of total spacing in a row
if let collection = self.collectionView {
let width = (collection.bounds.width - totalSpacing)/numberOfItemsPerRow
return CGSize(width: width, height: width * 1.2)
} else {
return CGSize(width: 0, height: 0)
}
}
Thanks!
You can embed view with constraints to edges as:
extension UIView {
func makeEdges(to view: UIView, useMargins: Bool = false) -> [NSLayoutConstraint] {
return [
(useMargins ? layoutMarginsGuide.leftAnchor : leftAnchor).constraint(equalTo: view.leftAnchor),
(useMargins ? layoutMarginsGuide.rightAnchor : rightAnchor).constraint(equalTo: view.rightAnchor),
(useMargins ? layoutMarginsGuide.topAnchor : topAnchor).constraint(equalTo: view.topAnchor),
(useMargins ? layoutMarginsGuide.bottomAnchor : bottomAnchor).constraint(equalTo: view.bottomAnchor)
]
}
func edges(to view: UIView, useMargins: Bool = false) {
NSLayoutConstraint.activate(makeEdges(to: view, useMargins: useMargins))
}
}
And use it as:
let cardView = ...
cardView.translatesAutoresizingMaskIntoConstraints = false
let cell = ...
cell.contentView.addSubview(cardView)
cell.contentView.edges(to: cardView, useMargins: true)
First take collectionview from storyboard. Set it's constraints like as below :-
Leading space to container - 20
Trailing space to container - 20
Top space to container - 20
Bottom space to container - 20
Then select collectionview and remove lines padding that is default 10. So, update with 20. So, cellpadding should be 10 each side.
Then Go to the viewcontroller file and add all 3 delegates
1. UICollectionViewDelegate
2. UICollectionViewDataSource
3. UICollectionViewDelegateFlowLayout
Then add following code in your swift file :-
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (self.view.frame.size.width - 60) / 2, height: (self.view.frame.size.width - 60) / 2)
}
I am using this code
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let padding: CGFloat = 50
let collectionViewSize = collectionView.frame.size.width - padding
return CGSize(width: collectionViewSize/2, height: collectionViewSize/2)
}
I am able to get a 2 column collection view on all iPhones except iPhone X and iphone XR, I don't know why
How to force 2 columns for all iPhones?
You can set layout of your collectionView by creating new layout and set it's itemSize, minimumInteritemSpacing and minimumLineSpacing and then assign new layout as collectionView.collectionViewLayout:
func setCollectionViewLayout(withPadding padding: CGFloat) {
let layout = UICollectionViewFlowLayout()
let size = (collectionView.frame.width - padding) / 2
layout.itemSize = CGSize(width: size, height: size)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView.collectionViewLayout = layout
}
and then call this method in viewDidLayoutSubviews (this is moment when frames are loaded and you can calculate with collectionView's frame)
override func viewDidLayoutSubviews() {
setCollectionViewLayout(withPadding: 50)
}
Note: I would recommend you to set leading and trailing constraints of collectionView to constant 25 instead of using padding
I suggest that you calculate width according to safeAreaLayoytGuide and, if you're using UICollectionViewFlowLayout, sectionInset. For UICollectionViewFlowLayout the following code will calculate proper width:
let sectionInset = (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset
let width = collectionView.safeAreaLayoutGuide.layoutFrame.width
- sectionInset.left
- sectionInset.right
- collectionView.contentInset.left
- collectionView.contentInset.right
If you need two columns, than item width will be calculated like that:
let space: CGFloat = 10.0
let itemSize = CGSize(width: (width - space) / 2, height: 100 /*DESIRED HEIGHT*/)
Note, I have scoured the internet and have not found a place to both size and centers cells that works. I tried doing it myself but I keep running to bugs I can't avoid. I am new to Swift. My code:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath:IndexPath) -> CGSize {
let cellWidth = collectionView.frame.size.width / 7.0
let cellHeight = collectionView.frame.height - 4.0
let imageSideLength = cellWidth < cellHeight ? cellWidth : cellHeight
return CGSize(width: imageSideLength, height: imageSideLength)
}
//centers the cells
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
// Make sure that the number of items is worth the computing effort.
guard let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout,
let dataSourceCount = photoCollectionView.dataSource?.collectionView(photoCollectionView, numberOfItemsInSection: section),
dataSourceCount > 0 else {
return .zero
}
let cellCount = CGFloat(dataSourceCount)
let itemSpacing = flowLayout.minimumInteritemSpacing
let cellWidth = flowLayout.itemSize.width + itemSpacing
let cellHeight = flowLayout.itemSize.height
var insets = flowLayout.sectionInset
// Make sure to remove the last item spacing or it will
// miscalculate the actual total width.
let totalCellWidth = (cellWidth * cellCount) - itemSpacing
let contentWidth = collectionView.frame.size.width - collectionView.contentInset.left - collectionView.contentInset.right
let contentHeight = collectionView.frame.size.height
// If the number of cells that exist take up less room than the
// collection view width, then center the content with the appropriate insets.
// Otherwise return the default layout inset.
guard totalCellWidth < contentWidth else {
return insets
}
// Calculate the right amount of padding to center the cells.
let padding = (contentWidth - totalCellWidth) / 2.0
insets.left = padding
insets.right = padding
insets.top = (contentHeight - cellHeight) / 2.0
//insets.bottom = (contentHeight - cellHeight) / 2.0
return insets
}
}
I try to use two separate functions: the first to size the cells and the second to center the cells. (Note I only want new cells to expand horizontally, with a maximum of 6 cells.) However, my calculation of cell height and width in the 2nd function does not agree with how I set it in the first function, setting off a chain of issues. Any insight on how to both size and center the cells such that I can have 1-6 cells horizontally fit on my screen centered would be great.
Your layout calls are conflicting. Try following THIS Tutorial to get the hang of it.
Otherwise a good answer for this is HERE
var flowLayout: UICollectionViewFlowLayout {
let _flowLayout = UICollectionViewFlowLayout()
// edit properties here
_flowLayout.itemSize = CGSize(width: 98, height: 134)
_flowLayout.sectionInset = UIEdgeInsetsMake(0, 5, 0, 5)
_flowLayout.scrollDirection = UICollectionViewScrollDirection.horizontal
_flowLayout.minimumInteritemSpacing = 0.0
// edit properties here
return _flowLayout
}
Set it with:
self.collectionView.collectionViewLayout = flowLayout // after initializing it another way
// or
UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
I'm really in need of some help here. I've been trying for a few days but can't seem to fix it...
I'm trying to lay out images in a UICollectionView grid style, with scrolling disabled. So I have 4 images/cells, and I'm trying to completely fill the UICollectionView with these images with 1 spacing between them.
The problem, however is that the spacing between the cells is split between center and the bottom.
I've noticed that when changing the scrollDirection of the UICollectionView from vertical to horizontal, the problem still exists, but just changes direction too, so I'm guessing it has something to do with that.
example:
code:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if isInFeed && self.postImages!.count > 1 {
guard let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout else {
return CGSize()
}
flowLayout.minimumInteritemSpacing = 1
flowLayout.minimumLineSpacing = 1
switch (self.postImages!.count) {
case 2:
// we split the collectionView into 2 parts (+ 2*0.5 spacing)
return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: self.collectionView!.bounds.height)
case 3:
// we split the collectionView into 3 parts, the first one taking up half, the other 2 images taking up 1/4th (+spacing)
if indexPath.row == 0 { return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: self.collectionView!.bounds.height) }
else { return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: (self.collectionView!.bounds.height/2)-CGFloat(0.5)) }
case 4:
// we split the collectionView into 4 parts (1/4th + spacing)
// if indexPath.section == 0 { return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: (self.collectionView!.bounds.height/2)-CGFloat(1))}
// else {return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: (self.collectionView!.bounds.height/2)-CGFloat(0))}
return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: (self.collectionView!.bounds.height/2)-CGFloat(0.5))
default: break
}
}
// If not in Feed or just one image, take up entire collectionView
return CGSize(width: self.collectionView!.bounds.width, height: self.collectionView!.bounds.height)
}
I also tried subclassing UICollectionViewLayout and UICollectionViewFlowLayout, but without success, I've read Apple's documentation, but that doesn't seem to help much.
Hope this explains the problem clearly, I'd be happy to elaborate.
Have a great day!
Edit:
I changed the scroll direction from vertical to horizontal and added 2 extra calculated spacing to each cell just to show off what happens if I do that.
Edit 2:
Finally managed to fix it! (altough not as clean as I'd like it, it does work now)
I put the following code inside of the collectionViewLayout method:
if indexPath.section == 0 {
flowLayout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 1 * UIScreen.main.scale)
} else {
flowLayout.sectionInset = UIEdgeInsets.zero
}
Big thanks to everyone trying to help out!
The final solution that worked for me (for anyone that runs into a similar situation):
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
if section == 0 {
return UIEdgeInsetsMake(0, 0, 0, 1 * UIScreen.main.scale)
} else {
return UIEdgeInsets.zero
}
}
Try this code. Add in a function and call in view did load. You might need to make a little adjustments but it works in my case.
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
let width = UIScreen.main.bounds.width
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: width / 2, height: width / 2)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView!.collectionViewLayout = layout
In Storyboard try with Size Inspector with Minimum Spacing Zero and all Section Insects zero.
layout.minimumLineSpacing = 1.0f;
layout.minimumInteritemSpacing = 1.0f;
CGSize collectionSize = collectionView.frame.size;
CGFloat cellsPerRow = 2.0f;
CGFloat spacing = 0.5f;
CGFloat itemWidth = collectionSize.width/cellsPerRow - spacing;
CGFloat itemHeight = collectionSize.height/cellsPerRow - spacing;
return CGSizeMake(itemWidth,itemHeight);
I've followed this answer and try to implement 5 cells per row and it's working great when I check on iPhone 6 & iPhone SE as below.
But the problem occures when I try to run it on iPhone 6 Plus. Can anyone help me out on figuring out the issue please?
This is my code.
screenSize = UIScreen.main.bounds
screenWidth = screenSize.width
screenHeight = screenSize.height
let itemWidth : CGFloat = (screenWidth / 5)
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.itemSize = CGSize(width: itemWidth, height: itemWidth)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.sectionInset = UIEdgeInsets(top: 10.0, left: 0, bottom: 10.0, right: 0)
collectionView!.collectionViewLayout = layout
It has to be
let itemWidth : CGFloat = (screenWidth / 5.0)
So the result will not be rounded.
Updated
Please make sure if you use storyboard to create your UICollectionView, remember to set autolayout to the collection view's size so that it is updated to whatever current screen size is.
Update 2
If you use storyboard there is no need to create a UICollectionViewFlowLayout. You can set the insets and spacings from storyboard.
Then in your .m file implement this to determine item's size.
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return yourDesiredSize;
}
To fix the blank spaces the size of each cell should be a round number. Then the difference between the sum of rounded numbers and the size of collectionView can be equally distributed or put in one cell.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// To avoid white space inbetween cells we're rounding the width of each cell
var width = CGFloat(floorf(Float(screenWidth/CGFloat(objects.count))))
if indexPath.row == 0 {
// Because we're rounding the width of each cell the cells don't cover the space completly, so we're making the first cell a few pixels wider to make sure we fill everything
width = screenWidth - CGFloat(objects.count-1)*width
}
return CGSize(width: width, height: collectionView.bounds.height)
}