I have a horizontal UICollectionView which works fine. But I want to center cell when there is no need for scrolling. i.e.
if all cell are able to fit within the view width & user don't need to scroll then Do center the cell
if all cell are not able to fit within the view width & user need to scroll then Don't center the cell (default behaviour)
Graphical example:
UICollectionView that needs scrolling then keep default behavior
UICollectionView that doesn't need scrolling
What I have tried so far:
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var dataList:[String] = ["icon_rotate", "icon_rotate2", "icon_rotate3", "icon_rotate4", "icon_rotate5", "icon_rotate6"]
override func viewDidLoad() {
super.viewDidLoad()
//other stuff
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return (UIDevice.current.userInterfaceIdiom == .pad) ? 20.0 : 10.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return (UIDevice.current.userInterfaceIdiom == .pad) ? 20.0 : 10.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.height * 0.75, height: collectionView.frame.size.height * 0.75)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataList.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "editorCell", for: indexPath) as! EditorCell
cell.iconView.image = UIImage(named: dataList[indexPath.row])
return cell
}
}
This results in default behavior of UICollectionView so I searched in SO and found this post: How to center horizontally UICollectionView Cells?
and I added this code:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
let totalCellWidth = CellWidth * CellCount
let totalSpacingWidth = CellSpacing * (CellCount - 1)
let leftInset = (collectionViewWidth - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
let rightInset = leftInset
return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)
}
This code does work fine when there's few cells to be loaded but I want this to dynamically detect if centering is needed or let the default behavior stay in action.
Thanks
Assuming your current code is working when you the cells will fit - that is, it centers the "row of cells" when desired - change this line:
let leftInset = (collectionViewWidth - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
to:
let leftInset = max(0, (collectionViewWidth - CGFloat(totalCellWidth + totalSpacingWidth)) / 2.0)
That way your insets will never be less than Zero
Related
I am working on the collectionView layout as below:
But the last row is not getting align properly not sure why?
Here is the collectionView setup code:
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 7
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "\(collectionViewCell.self)", for: indexPath) as? collectionViewCell {
cell.imageView.backgroundColor = .black
return cell
}
return UICollectionViewCell()
}
Here is the implementation of the flow layout delegate method:
let sectionInsets = UIEdgeInsets(top: 30.0, left: 20.0, bottom: 30.0, right: 20.0)
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.row == 2 { // This item should occupy the entire available width
let paddingSpace = sectionInsets.left + sectionInsets.right
let availableWidth = collectionView.frame.width - paddingSpace
let widthPerItem = availableWidth
return CGSize(width: widthPerItem, height: (widthPerItem / 2))
}
// adjust 2 cells in the single row
let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
let availableWidth = collectionView.frame.width - paddingSpace
let widthPerItem = availableWidth / itemsPerRow
return CGSize(width: widthPerItem, height: widthPerItem)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAt section: Int) -> UIEdgeInsets {
return sectionInsets
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return sectionInsets.left
}
What you are seeing is normal for a flow layout. All rows are left and right justified except for the last row, which is left justified only. It’s just like a printed book: the last line of a paragraph does not get spaced out to touch the right margin.
I had the same problem. But I could fix by setting collectionView(_:layout:minimumInteritemSpacingForSectionAt:).
I dragged a collectionview on to my xib, set the constraints as leading,trailing,top and height.
The coding part is given as...
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 5
layout.minimumInteritemSpacing = 5
collectionview2.delegate = self
collectionview2.dataSource = self
collectionview2.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "ident")
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
switch indexPath.item {
case 0,1:
return CGSize(width: (UIScreen.main.bounds.width - 16) / 2, height: (UIScreen.main.bounds.width - 16) / 2)
default:
return CGSize(width: (UIScreen.main.bounds.width - 32) / 3, height: (UIScreen.main.bounds.width) / 3)
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ident", for: indexPath)
cell.backgroundColor = .red
return cell
}
This is the link I have referred.
But it is not working and I'm seeing just 5 equal width and height cells one after the other.
What am I doing wrong...?
EDIT 1:
I would also like to show one long cell on left and 2 small cells on right like so...
What changes do I make to my answer so that I can achieve this...?
Your code looks perfect but you may forgot to confirm the FlowLayout Protocol.
so,
you just need to confirm the below protocol in your code,
UICollectionViewDelegateFlowLayout
Hope this way may help you.
You've to try this
Confirm UICollectionViewDelegateFlowLayout
e.g.
class yourViewController: UIViewController,UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let screeSize = UIScreen.main.bounds
let numOfCell:Int = 3 //Number of items you want
let spaceOfCell:CGFloat = (CGFloat((numOfCell + 1)*10)) //Space between Cells
return CGSize(width: Int(screeSize.width - spaceOfCell) / numOfCell, height: Int(screeSize.width - spaceOfCell) / numOfCell)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10.00
//Verticle space between line
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 10.00 //Space Between Items You want
}
Hope it helps :)
I have implemented UICollectionView for rendering WKWebview as contentView's subview of UICollectionViewCell and bound UIPageControl to it.
Note: Just for demoing the problem while rendering, I have made the size of the cell width as 100
Problem is first Webview(Which is a cell in the first section of collection view) is showing as expected but the second cell displaying with an offset of cell width.
UICollectionViewDelegate Override methods
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1;
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// set this width for just showing screenshot for this question
return CGSize(100, height: view.frame.height-view.safeAreaInsets.top);
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0;
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
self.webviewsPageControl.currentPage = indexPath.section;
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
self.webviewsPageControl.numberOfPages = self.webviews?.count ?? 0;
return self.webviews?.count ?? 0;
}
For identification, I made the UICollectionViewCell background as red.
So the question is, how to remove the offset so that webviews can be rendered side by side. And Why is this behavior.
Thanks in advance.
You need to set the minimumLineSpacing = 0 on the flow layout, e.g.
let layout = UICollectionViewFlowLayout();
layout.minimumLineSpacing = 0
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
I use the following code to reduce spacing between columns:
let cellsPerRow = 2 //number of columns or number of cells in each row
In viewDidLoad():
guard let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout else { return }
flowLayout.minimumInteritemSpacing = 0 //setting margin between cell columns = 0
flowLayout.minimumLineSpacing = 0// setting margin between cell lines = 0
flowLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) //Not required, solutions works without it as well
Finally,
//function to set number of columns
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
let marginsAndInsets = flowLayout.sectionInset.left + flowLayout.sectionInset.right + flowLayout.minimumInteritemSpacing * CGFloat(cellsPerRow - 1)
let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down)
return CGSize(width: itemWidth, height: itemWidth)
}
So I have a CollectionView that has the following code:
class MyViewController: UIViewController {
...
#IBOutlet weak var imageCollectionView: UICollectionView!
fileprivate let sectionInsets = UIEdgeInsets(top: 1, left: 1, bottom: 1, right: 1)
fileprivate let itemsPerRow: CGFloat = 3
...
override func viewDidLoad() {
super.viewDidLoad()
imageCollectionView.delegate = self
imageCollectionView.dataSource = self
}
}
...
extension MyViewController : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
let availableWidth = collectionView.bounds.width - paddingSpace
let widthPerItem = availableWidth / itemsPerRow
return CGSize(width: widthPerItem, height: widthPerItem)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAt section: Int) -> UIEdgeInsets {
return sectionInsets
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return sectionInsets.left
}
}
What I'm going for is 3 cells per row with very little space between but whenever I run the app I get something like this:
Where each black block is a cell.
For similar questions I've found, the answer is usually to implement the UICollectionViewDelegateFlowLayout functions which I've done and my functions are being called but just not working as expected! Even if I manually change the widthPerItem value to be slightly smaller, it still doesn't work!
Can anyone see if I'm missing something obvious here?
Thank you!
Did you make sure the minimum inter-item spacing is set to only 1 point?
You can set this up in Interface Builder or by overriding the following delegate method:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1.0;
}
Another possible reason I see is that the result of dividing availableWidth by itemsPerRow is a floating number. In the end the sum of the cell widths and the inset and might actually exceed the collection view width by 0.x pixels. To make sure this doesn't happen, you may want to use floor:
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
let availableWidth = collectionView.bounds.width - paddingSpace
let widthPerItem = floor(availableWidth / itemsPerRow)
return CGSize(width: widthPerItem, height: widthPerItem)
}
I have implemented my UICollectionView like this
extension MyViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return photos.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath)
cell.backgroundColor = .black
return cell
}
}
extension MyViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let widthPerItem = view.frame.width / 3
return CGSize(width: widthPerItem, height: widthPerItem)
}
}
but instead of 3 items in a row I'm getting 2 items
how can i fix it? i didn't set any UIEdgeInset for my items and sections
Try this:
extension MyViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let numRowItems:CGFloat = 3
let padding:CGFloat = 2
let width = (collectionView.bounds.width / numRowItems) - padding
let height = collectionView.bounds.height - (2 * padding)
return CGSize(width: width, height: height)
}
}
You forgot to account for minimumInteritemSpacing and minimumLineSpacing properties of UICollectionViewFlowLayout - they both default to 10.0 if you didn't set them.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let spacing : CGFloat = (collectionViewLayout as? UICollectionViewFlowLayout)?.minimumInteritemSpacing ?? 0.0
let widthPerItem = (view.frame.width - spacing * 2)/ 3
return CGSize(width: widthPerItem, height: widthPerItem)
}
This is because your screan width can not fit 3 cells. This is because collection view has cell min line spacing property in your storyboard if you try with small width in sizeForItemAt they will fit.
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let widthPerItem = view.frame.width / 3 - ((collectionViewLayout as? UICollectionViewFlowLayout)?.minimumInterItemSpacing ?? 0.0)
return CGSize(width: widthPerItem, height: widthPerItem)
}
in obj c
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
//If you want your cell should be square in size the return the equal height and width, and make sure you deduct the Section Inset from it.
return CGSizeMake((self.view.frame.size.width/2) - 16, (self.view.frame.size.width/2) - 16);
}
swift
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// Compute the dimension of a cell for an NxN layout with space S between
// cells. Take the collection view's width, subtract (N-1)*S points for
// the spaces between the cells, and then divide by N to find the final
// dimension for the cell's width and height.
let cellsAcross: CGFloat = 3
let spaceBetweenCells: CGFloat = 1
let dim = (collectionView.bounds.width - (cellsAcross - 1) * spaceBetweenCells) / cellsAcross
return CGSize(width: dim, height: dim)
}