I have a CollectionView, and what I want to do is, that last element, as it has space, to center it.
This is my code, I have the class that inherits from UICollectionView that does not have more than the obligatory methods.
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: (view.frame.size.width/3) - 3,
height: (view.frame.size.width/3) - 3)
layout.sectionInset = UIEdgeInsets(top: 100, left: 40, bottom: 40, right: 40)
layout.minimumLineSpacing = 40
layout.minimumInteritemSpacing = 40
let cv = CollectionViewController(collectionViewLayout: layout)
present(cv, animated: true, completion: nil)
Take a look at implementing the sizeForItemAt delegate function.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// Do your logic here:
// Find the last item:
if indexPath.item == YOURARRAY.last {
// Logic for centering
}
return CGSize(width: (view.frame.size.width/3) - 3,
height: (view.frame.size.width/3) - 3)
}
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:
Using UICollectionView under the UITableViewCell
Using below extension for creating dashed border:
extension UIView {
func addDashedLineView(frame: CGRect) {
let border = CAShapeLayer()
border.strokeColor = UIColor.blue.cgColor
border.lineDashPattern = [6, 6]
border.frame = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)//self.frame
border.fillColor = UIColor.clear.cgColor
border.lineJoin = kCALineJoinRound
//print("widht: \(frame.width) fraheight: \(UIScreen.main.bounds.width)")
border.fillColor = nil
border.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: frame.width, height: frame.height), cornerRadius: 6).cgPath
self.clipsToBounds = true
self.layer.addSublayer(border)
}
}
Below is my collection view method where i am calling above method (actually the cell width depending upon the content size so i am calculating the text width) the issue is when the cell are dequeue from the list the border get redraw from different width, also i am using custom cell "CollectionViewCell" class:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: CollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
let width = self.collectionData[indexPath.row].width(withConstraintedHeight: 14, font: .systemFont(ofSize: 17)) + Constant.cellPadding
cell.contentView.addDashedLineView(frame: CGRect(x: 0, y: 0, width: width, height: Constant.itemHeight))
cell.contentView.setNeedsLayout()
cell.contentView.layoutIfNeeded()
cell.layoutIfNeeded()
return cell
}
When first load drawing border correctly when scroll the view it has been redraw
How to resolve the issue
I want to calculate the height of the text to get the estimated height for the collection view cell. I use the following code inside collectionViewLayout function;
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if let messageText = messages[indexPath.row]?.text {
let size = CGSize(width: view.frame.width, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: messageText).boundingRect(with: size, options: options, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 17)], context: nil)
return CGSize(width: view.frame.width, height: estimatedFrame.height + 20)
}
return CGSize(width: view.frame.width, height: 100)
}
This is working for system fonts, but not for my custom font inside my project. The problem is that the estimatedFrame is not equal to the result with a system font. I think the problem will be the param of options: attributes. Is there a way like UIFont.systemFont(ofSize: 17) for custom fonts?
If I'm reading your question right, you want:
UIFont(name: "yourCustomFontNameString", size: 17)
So:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if let messageText = messages[indexPath.row]?.text {
let size = CGSize(width: view.frame.width, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: messageText).boundingRect(with: size, options: options, attributes: [NSAttributedStringKey.font: UIFont(name: "yourCustomFontNameString", size: 17)], context: nil)
return CGSize(width: view.frame.width, height: estimatedFrame.height + 20)
}
return CGSize(width: view.frame.width, height: 100)
}
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
}