Swift: if the element in a collection view has space to spare, center it - swift

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)
}

Related

align single UICollectionViewCell to the left of the collectionView

I've got a collectionView that is inside a tableView cell. The collectionView has multiple sections. If a section has only one cell, the cell appears centered. How can I align that single cell to the left?
The collectionView has the following flowLayout:
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .vertical
flowLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
flowLayout.minimumInteritemSpacing = 0
flowLayout.minimumLineSpacing = 0
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
collectionView.setCollectionViewLayout(flowLayout, animated: true)
Since sectionInset is the same as func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { } protocol, one could call this protocol to know which section has only one cell left.
You can replace :
flowLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
With:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
if collectionView.numberOfItems(inSection: section) == 1 {
let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: collectionView.frame.width - flowLayout.itemSize.width)
}
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
This red cell is the only one left in the second section , and it is left aligned:
I had same issue.
My solution is to change of collectionView's estimate size.
Automatic -> None

How to add a UIPageController and UICollectionViewController to a UIViewController programmatically

I already have a UIViewController made programmatically, and would like at the bottom to have a collection view with 4 cells, but I want to be able to swipe through different pages of the collection view to see different cells. I'm not sure where to begin with this wether to enable paging on the collection view and how that would work with setting up the cells, or to create a page controller and adding the collection view to that? There are a couple of ways that I have seen online already, but that don't really fit my needs.
I would like something as such:
Let me know if I can provide you with more information. I just created a basic page controller but am not sure how to achieve what I'm looking for.
Edit: I created a collection view and added the constraints to get the layout I want; however I'm not sure how to make it swipe like a page.
Here's the code for the collection view:
let friendsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.register(FriendsCell.self, forCellWithReuseIdentifier: "cell")
cv.backgroundColor = UIColor.blue
cv.layer.cornerRadius = 10
return cv
}()
view.addSubview(friendsCollectionView)
friendsCollectionView.anchor(top: separatorView.bottomAnchor, left: nil, bottom: nil, right: nil, paddingTop: 50, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 250, height: 250)
friendsCollectionView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 75, height: 75)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
if section == 0 {
return UIEdgeInsets(top: 85, left: 10, bottom: 0, right: 0)
}
if section == 1 {
return UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)
}
if section == 2 {
return UIEdgeInsets(top: 85, left: 5, bottom: 0, right: 0)
}
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0 { return 1 }
if section == 1 { return 2 }
if section == 2 { return 1 }
return 0
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 3
}
All you need would be a UICollectionViewController or as I am assuming you have, a UIViewController that conforms to the collectionView protocols. Your UIViewController with one UICollectionView is a good starting point. However the cell you insert needs to be different. First of all, the collectionView within the ViewController needs to have collectionView?.isPagingEnabled = true and its layouts scrollDirection set to .horizontal. Having done that you need to create your cells.
The cells you implement should be a subclass of UICollectionViewController and UICollectionViewDelegateFlowLayout. This collectionView's layout needs to have scrollDirection set to .vertical although I believe you are not scrolling in this view anyways. The number of cells in this collectionView will be 4. You then dequeue your cell of which you want to have 4 (the one with the head in white on blue).
The UICollectionViewController needs to then be dequeued as the cell for your first collectionView. Basically:
Your main ViewController has a collectionView that scrolls horizontally. This collectionView has a UICollectionViewController as cells and scrolls vertically.
It may sound very complicated but I have done it and it works smoothly. Let me know if you have any questions.

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)

Left Align UICollectionViewCells only works when cells are not correct height, and stops working when scrolled

I am trying to left align the cells in a UICollectionView, and I have used a GitHub repo to try and obtain this, link here:
https://github.com/mischa-hildebrand/AlignedCollectionViewFlowLayout
I set up the CollectionView and its cells like so:
lazy var filterCollectionView: UICollectionView = {
let layout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, verticalAlignment: .center)
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.register(filterCell.self, forCellWithReuseIdentifier: "filterCellId")
cv.backgroundColor = .white
cv.isScrollEnabled = true
cv.dataSource = self
cv.delegate = self
cv.showsHorizontalScrollIndicator = false
return cv
}()
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return genrArray.count
}
let genArray = ["Test123", "LongTextTest", "Short", "S", "#LongLongLongLong"]
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "filterCellId", for: indexPath) as! filterCell
cell.genText.text = genArray[indexPath.row]
cell.genText.sizeToFit()
cell.frame = CGRect(x: cell.frame.origin.x, y: cell.frame.origin.y, width: cell.genText.frame.width + 25, height: 24)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let label = UILabel(frame: .zero)
label.text = genArray[indexPath.row]
label.frame = CGRect(x: 0, y: 0, width: label.intrinsicContentSize.width, height: 0)
return CGSize(width: label.frame.width + 25, height: 18)
}
This did nothing to the cells, so I added these lines of code into ViewDidLoad():
let layout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, verticalAlignment: .center)
layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
self.filterCollectionView.collectionViewLayout = layout
The line that actually made a change in the CollectionView was layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize. But this still did not achieve the desired effect. This squished my cells and made them have a height of much smaller than 18 (the size declared in sizeForItem. When loaded up, the collectionView looks like this:
But when I scroll through the collectionView, it reverts back to normal as if the left alignment layout is not even there. It then looks like this:
The cells are the correct shape in that image, but the alignment/layout/spacing is off.
What am I doing wrong?

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
}