CollectionView Cells won't be centred in view, no matter what I do - swift

I'm having trouble with centering my CollectionView Cells on the Collection View.
This is what I have now, found here on Stackoverflow (but tried few other solutions that did not work):
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let totalCellWidth = cellWidth * CGFloat(posts.count)
let totalSpacingWidth = CGFloat(cellSpacing * (posts.count - 1))
let collectionViewWidth = collectionView.frame.width
let leftInset = (collectionViewWidth - (totalCellWidth + totalSpacingWidth)) / 2
let rightInset = leftInset
return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)
}
I tried writing my own solutions but nothing worked.
cellWidth, cellHeight and cellSpacing are fixed numbers.
This is what I get:

Related

collectionview item will not in the center after scrolling

I am now making a collectionView for pics and I wanna make it like below.
However, when I scroll to the right/left, the next item will never in the center and I tried so many methods already still cannot solve the problem.
Can anyone help me?
Below is the code.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let cellCount = CGFloat(Config.banners.count)
if cellCount > 0 {
let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
let cellWidth = screenSize.width - flowLayout.minimumInteritemSpacing
flowLayout.itemSize = CGSize(width: cellWidth, height: 250)
flowLayout.minimumLineSpacing = -60
let totalCellWidth = cellWidth*cellCount
let totalSpacingWidth = 30 * (cellCount - 1)
let contentWidth = collectionView.frame.size.width
if (totalCellWidth < contentWidth) {
print("totalCellWidth < contentWidth")
let padding = (contentWidth - totalCellWidth) / 2.0
return UIEdgeInsets(top: 0, left: padding, bottom: 0, right: padding)
} else {
print("totalCellWidth > contentWidth")
return UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0)
}
}
return UIEdgeInsets.zero
}
add these lines collectionviewflowdelegate method in your code. It will work. I achieved the same thing by adding these methods.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = UIScreen.main.bounds.width / 3.0
let height = collectionView.frame.size.height
return CGSize(width: width, height: height)
}
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
}

UICollectionViewCell zero spacing not working

I did create a custom calendar based on UICollectionView.
One UICollectionViewCell - one date in the calendar. I want to delete spacing between cells.
CollectionView layout settings -
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (collectionView.frame.width / 7), height: collectionView.frame.width / 7)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(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
}
In different screen sizes, unless iPhone X, we will see the spacing between cells.
How to fix it?
You probably have a rounding error. You are saying
collectionView.frame.width / 7
An iPhone 11 is 414 points wide. 414 / 7 is 59.14285714. There is no way to portray a fractional point on the screen, so now what? We round down, and now there is an extra pixel space between cells.
This is occurring because of the rounding-off of decimal places when your providing collectionView.frame.width / 7 just as matt has said in his answer. Seems you are trying to avoid this because you don't want users to see that bit of gap between the cells. Mostly you would only see this on a simulator an not on a real device due to the pixel density. You can overcome this fault either by replacing the UICollectionViewFlowLayout with a UICollectionViewCompositionalLayout which is available only from iOS 12. A simple fix would be to create a custom background view for the cell that extends the UICollectionViewCell by just 1 pixel. Here is an example of how I tried to achieve this:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .purple
let view = UIView()
view.backgroundColor = .brown
cell.addSubview(view) // Comment this line and you can see the gap
view.frame = cell.bounds
view.frame.size = CGSize(width: view.frame.size.width + 1, height: view.frame.size.height + 1)
return cell
}
This was a nice little hack I found a while back. Hope this helps you too.
I found a solution -
1. Delete from storyboard leading and trailing constraints from calendar view.
2. Set center horizontally constraint
3. Set width constraint to calendar view.
4. Create #IBOutlet calendarWidthConstraint
5. In viewDidLoad -
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
let width = self.view.frame.width
let cellSize = CGFloat(width / 7).rounded()
let newWidth = cellSize * 7
layout.itemSize = CGSize(width: cellSize, height: cellSize)
calendarWidth.constant = newWidth
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView.collectionViewLayout = layout

Swift CollectionView slider with preview of next page

I have slider which display image on each cell. What I want to do, is display 10% of next image on current page and slider it to next page. Each image should be centered in own page (3 in total).
extension OnboardingViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width - 200, height: view.frame.height)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let cellWidth : CGFloat = view.frame.width - 200
let numberOfCells = floor(self.view.frame.size.width / cellWidth)
let edgeInsets = (self.view.frame.size.width - (numberOfCells * cellWidth)) / (numberOfCells + 1)
return UIEdgeInsets(top: 15, left: edgeInsets, bottom: 0, right: edgeInsets)
}
}
What i did is... i put left edge inset. Made my cell smaller, like 0.74 of initial size, i add 2 methods to control my slides when i drag slide...
then i add animation to my custom cell with flag previewed. So.. my initial slide always previewed and has size 1.0, all other has 0.9 size, when i drag image i change previewed state to false for all slides and do true only for presented.
0.74 is size of my cell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let cellWidth : CGFloat = view.frame.width * 0.74
let numberOfCells = floor(self.view.frame.size.width / cellWidth)
let edgeInsets = (self.view.frame.size.width - (numberOfCells * cellWidth)) / (numberOfCells + 1)
return UIEdgeInsets(top: 0, left: edgeInsets, bottom: 0, right: edgeInsets)
}
i found custom size to my case
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width * 0.74, height: view.frame.height)
}
And i wrote 2 methods to control my slides
func scrollViewWillEndDragging(_ scrollView: UIScrollView,
withVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let x = targetContentOffset.pointee.x
pageControl.currentPage = Int(x / view.frame.width * 0.74)
let last = viewModel.getNumberOfItems() - 1
if pageControl.currentPage == last {
btnStart.isHidden = false
imbBtnStartBg.isHidden = false
} else {
btnStart.isHidden = true
imbBtnStartBg.isHidden = true
}
}
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
let pageWidth = view.frame.width * 0.74
let page = floor((collectionView.contentOffset.x - pageWidth / 2) / pageWidth) + 1
let xOffset: CGFloat = page * pageWidth
collectionView.setContentOffset(CGPoint(x: xOffset, y: 0.0), animated: true)
pageControl.currentPage = Int(page)
if Int(page) == 2{
btnStart.isHidden = false
imbBtnStartBg.isHidden = false
} else {
btnStart.isHidden = true
imbBtnStartBg.isHidden = true
}
for i in 0..<viewModel.onBoardingModels.count{
viewModel.onBoardingModels[i].previewed = false
}
viewModel.onBoardingModels[Int(page)].previewed = true
collectionView.reloadData()
}
Also i add animation to my cell
UIView.animate(withDuration: 0.5, animations: {
self.imgv.transform = model.previewed ? CGAffineTransform(scaleX: 1, y: 1) : CGAffineTransform(scaleX: 0.9, y: 0.9)
})

Hide upper CollectionView cells with animation (Swift 4)

How can I make it so that when I scroll down the top cells are not trimmed like mine and hiding with animation?
My App:
Example:
I tried VegaScroll, but it does not match the description
You need to edit you layout using the Delegate, after Setting the Layout as Vega
i've achieved this using this Code.
//in viewDidLoad
let layout1 = VegaScrollFlowLayout()
collectionView.collectionViewLayout = layout1
And then using CollectionView layout Delegate
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.width / 3 - 1
return CGSize(width: width, height: width)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 1.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1.0
}
i don't see any code provided by you but i assume you override the Layout like this.
collectionView.collectionViewLayout = layout
layout.minimumLineSpacing = 20
layout.itemSize = CGSize(width: collectionView.frame.width, height: 87)
layout.sectionInset = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)
And that what causes the problem, Note the above code of the solution displays 3 cells per row.
Or you can simply achieve that by using this code in ViewDidLoad.
let layout1 = VegaScrollFlowLayout()
collectionView.collectionViewLayout = layout1
layout1.minimumLineSpacing = 1
let width = Col1.frame.width / 3 - 1
layout1.itemSize = CGSize(width: width , height: 87)
layout1.sectionInset = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)

How to set cell spacing and UICollectionView - UICollectionViewFlowLayout size ratio?

I'm trying to add UICollectionView to ViewController, and I need to have 3 cells 'per row' without blank space between cells (it should look like a grid). Cell width should be one third of screen size, so I thought that the layout.item width should be the same. But then I get this:
If I reduce that size (by 7 or 8 pixels e.g.), it's better, but the third cell in row is not completely visible, and I still have that blank space (top & bottom, and left & right) .
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
var collectionView: UICollectionView?
var screenSize: CGRect!
var screenWidth: CGFloat!
var screenHeight: CGFloat!
override func viewDidLoad() {
super.viewDidLoad()
screenSize = UIScreen.mainScreen().bounds
screenWidth = screenSize.width
screenHeight = screenSize.height
// Do any additional setup after loading the view, typically from a nib
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 0, bottom: 10, right: 0)
layout.itemSize = CGSize(width: screenWidth / 3, height: screenWidth / 3)
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView!.dataSource = self
collectionView!.delegate = self
collectionView!.registerClass(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell")
collectionView!.backgroundColor = UIColor.greenColor()
self.view.addSubview(collectionView!)
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 20
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as CollectionViewCell
cell.backgroundColor = UIColor.whiteColor()
cell.layer.borderColor = UIColor.blackColor().CGColor
cell.layer.borderWidth = 0.5
cell.frame.size.width = screenWidth / 3
cell.frame.size.height = screenWidth / 3
cell.textLabel?.text = "\(indexPath.section):\(indexPath.row)"
return cell
}
}
Add these 2 lines
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
So you have:
// Do any additional setup after loading the view, typically from a nib.
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 0, bottom: 10, right: 0)
layout.itemSize = CGSize(width: screenWidth/3, height: screenWidth/3)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView!.collectionViewLayout = layout
That will remove all the spaces and give you a grid layout:
If you want the first column to have a width equal to the screen width then add the following function:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if indexPath.row == 0
{
return CGSize(width: screenWidth, height: screenWidth/3)
}
return CGSize(width: screenWidth/3, height: screenWidth/3);
}
Grid layout will now look like (I've also added a blue background to first cell):
For Swift 3 and XCode 8, this worked. Follow below steps to achieve this:-
{
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
let width = UIScreen.main.bounds.width
layout.sectionInset = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
layout.itemSize = CGSize(width: width / 2, height: width / 2)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView!.collectionViewLayout = layout
}
Place this code into viewDidLoad() function.
In Certain situations, Setting the UICollectionViewFlowLayout in viewDidLoador ViewWillAppear may not effect on the collectionView.
Setting the UICollectionViewFlowLayout in viewDidAppear may cause see the changes of the cells sizes in runtime.
Another Solution, in Swift 3 :
extension YourViewController : UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 20, left: 0, bottom: 10, right: 0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let collectionViewWidth = collectionView.bounds.width
return CGSize(width: collectionViewWidth/3, height: collectionViewWidth/3)
}
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 20
}
}
If you are looking for Swift 3, Follow the steps to achieve this:
func viewDidLoad() {
//Define Layout here
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
//Get device width
let width = UIScreen.main.bounds.width
//set section inset as per your requirement.
layout.sectionInset = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
//set cell item size here
layout.itemSize = CGSize(width: width / 2, height: width / 2)
//set Minimum spacing between 2 items
layout.minimumInteritemSpacing = 0
//set minimum vertical line spacing here between two lines in collectionview
layout.minimumLineSpacing = 0
//apply defined layout to collectionview
collectionView!.collectionViewLayout = layout
}
This is verified on Xcode 8.0 with Swift 3.
let layout = myCollectionView.collectionViewLayout as? UICollectionViewFlowLayout
layout?.minimumLineSpacing = 8
Swift 4, Swift 5, Easiest Way!
No need create UICollectionViewFlowLayout() instance, just implement UICollectionViewDelegateFlowLayout on your Class.
extension MyCollectionView: UICollectionViewDelegateFlowLayout {
// Distance Between Item Cells
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 20
}
// Cell Margin
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
}
}
Swift 4
let collectionViewLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
collectionViewLayout?.sectionInset = UIEdgeInsetsMake(0, 20, 0, 40)
collectionViewLayout?.invalidateLayout()
Swift 5 : For evenly distributed spaces between cells with dynamic cell width to make the best of container space you may use the code snippet below by providing a minimumCellWidth value.
private func collectionViewLayout() -> UICollectionViewLayout {
let layout = UICollectionViewFlowLayout()
layout.sectionHeadersPinToVisibleBounds = true
// Important: if direction is horizontal use minimumItemSpacing instead.
layout.scrollDirection = .vertical
let itemHeight: CGFloat = 240
let minCellWidth :CGFloat = 130.0
let minItemSpacing: CGFloat = 10
let containerWidth: CGFloat = self.view.bounds.width
let maxCellCountPerRow: CGFloat = floor((containerWidth - minItemSpacing) / (minCellWidth+minItemSpacing ))
let itemWidth: CGFloat = floor( ((containerWidth - (2 * minItemSpacing) - (maxCellCountPerRow-1) * minItemSpacing) / maxCellCountPerRow ) )
// Calculate the remaining space after substracting calculating cellWidth (Divide by 2 because of left and right insets)
let inset = max(minItemSpacing, floor( (containerWidth - (maxCellCountPerRow*itemWidth) - (maxCellCountPerRow-1)*minItemSpacing) / 2 ) )
layout.itemSize = CGSize(width: itemWidth, height: itemHeight)
layout.minimumInteritemSpacing = min(minItemSpacing,inset)
layout.minimumLineSpacing = minItemSpacing
layout.sectionInset = UIEdgeInsets(top: minItemSpacing, left: inset, bottom: minItemSpacing, right: inset)
return layout
}
For Swift 3+ and Xcode 9+ Try using this
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let collectionWidth = collectionView.bounds.width
return CGSize(width: collectionWidth/3, height: collectionWidth/3)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
instead of writing a line of code, we have an option in XCode, select CollectionView and go to navigator and change the "Min Spacing"
For Swift 3 and XCode 8, this worked. Follow below steps to achieve this:-
viewDidLoad()
{
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
var width = UIScreen.main.bounds.width
layout.sectionInset = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
width = width - 10
layout.itemSize = CGSize(width: width / 2, height: width / 2)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView!.collectionViewLayout = layout
}