UICollectionView with flow layout won't put multiple items on one line - ios

I have a collectionView in which I am trying to create a grid style layout. I'm using a flowLayout and standard UICollectionViewCell's for now, but I can't seem to get the collectionView to put multiple items on the same line, as I believe the flowLayout should do by default. Here is my collectionView code:
View Controller initialization code:
override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .Horizontal
collectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: layout) super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
collectionView.backgroundColor = UIColor.whiteColor()
}
View Did Load:
collectionView.frame = self.view.bounds
collectionView.reloadData()
self.view.addSubview(collectionView)
Delegate and Data Source:
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSize(width: 40, height: 40)
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as UICollectionViewCell
func getRandomColor() -> UIColor{
var randomRed:CGFloat = CGFloat(drand48())
var randomGreen:CGFloat = CGFloat(drand48())
var randomBlue:CGFloat = CGFloat(drand48())
return UIColor(red: randomRed, green: randomGreen, blue: randomBlue, alpha: 1.0)
}
cell.backgroundColor = getRandomColor()
return cell
}
And this is what I get:
Shouldn't the cells build sideways until the edge of the collectionView's frame is hit, and then go to a new line?
Any idea why this isn't happening?
Thanks,

Do you have number of sections as 1?
If you have more than one section it will move the cells onto the next line by default. Setting the number of sections to 1 and number of cells to multiple will solve this.

Are you sure your method that gives the size is being called? I don't see layout.delegate = self anywhere in your code. Also, you can just specify itemSize in your initialization
var flowLayout = UICollectionViewFlowLayout()
flowLayout.itemSize = CGSizeMake(40, 40)
Also, do you really want to use scrollDirection = Horizontal? if that's the case then your collection view height should be equal to item height or you need to have enough item to fill the screen and more so that it scrolls

Related

Self-sizing UICollectionView with UITableView with dynamic header as a cell

I have a problem with making a self sizing UICollectionView with a cell that contains UITableView with a header that has a label with dynamic height.
Can someone point me out to what I need to change in the attached sample project?
You can see on the screenshot that the table does not fit the view as the cell's height is currently manually set.
import UIKit
class ViewController: UIViewController {
static let section1 = "section1"
static let section2 = "section2"
private weak var collectionView: UICollectionView!
override func viewDidLoad() {
self.navigationItem.title = "Collection View"
super.viewDidLoad()
self.setupCollectionView()
}
private func setupCollectionView() {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .vertical
flowLayout.minimumInteritemSpacing = 0.0
flowLayout.minimumLineSpacing = 0.0
let collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: flowLayout)
collectionView.alwaysBounceVertical = true
collectionView.register(
SectionHeaderView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: SectionHeaderView.sectionHeaderId
)
collectionView.register(Section1.self, forCellWithReuseIdentifier: ViewController.section1)
collectionView.register(Section2.self, forCellWithReuseIdentifier: ViewController.section2)
self.view.addSubview(collectionView)
self.collectionView = collectionView
self.collectionView.translatesAutoresizingMaskIntoConstraints = false
self.collectionView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.collectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.collectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
self.collectionView.dataSource = self
self.collectionView.delegate = self
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
switch indexPath.section {
case 0:
return CGSize(width: self.view.frame.width, height: 210.0)
case 1:
return CGSize(width: self.view.frame.width, height: 5 * 51.0 + 130.0) // How to enable self-sizing cells for table view inside
default:
fatalError("Unsupported section index.")
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: self.view.frame.width, height: 61)
}
}
// MARK: - UICollectionViewDataSource
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: SectionHeaderView.sectionHeaderId, for: indexPath) as! SectionHeaderView
switch indexPath.section {
case 0:
header.uiLabel.text = "Section 1"
case 1:
header.uiLabel.text = "Section 2"
default:
fatalError("Unsupported section index.")
}
return header
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch indexPath.section {
case 0:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ViewController.section1, for: indexPath) as! Section1
return cell
case 1:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ViewController.section2, for: indexPath) as! Section2
return cell
default:
fatalError("OverviewController: Unsupported section index.")
}
}
}
To implement auto-sizing collectionView cells, you really only need a few changes.
Couple key points:
Cells must satisfy their own constraints. So, instead of calculating sizing in sizeForItemAt, make sure the cells have width and height constraints. These can be dynamic, based on content.
Add elements to the collection view cell's contentView, not to the cell itself.
For the embedded non-scrolling table view, use a subclass that sets the intrinsic content size height based on the table's contentSize. Example:
final class ContentSizedTableView: UITableView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
There is a pretty good tutorial here (not mine): https://medium.com/#andrea.toso/uicollectionviewcell-dynamic-height-swift-b099b28ddd23 that has more detailed explanations.
I implement those concepts in the project you made available, and put it up on a GitHub repo (to make it easy to see the changes): https://github.com/DonMag/ThunderCollectionView
Results:

I am implementing UICollectionView with two rows of cells and I need both can be scroll horizontally at the same time

I have a UICollectionView that has horizontal scrolling. I need to place all my collection view cells in two rows and both should scroll horizontally. The layout is as shown in the screenshot.
As shown in the above screenshot, I am going to have to build two rows which will scroll horizontally, i.e., both rows will scroll together in the same direction.
I had earlier considered using sections in the scroll view, but then the scrolling would probably be independent, and so, I am hoping to find a better solution.
I looked into this link here : A similar post
This link uses a tableview to hold multiple collection views. Even though solution seems good, I am really not sure if it could work for me, I wish to know if there is a better alternative.
I looked into other stack overflow posts regarding the same (Can’t remember which though), but they did not help much.

Now normally, we would have two columns in the collection view and we can scroll vertically. Instead of this behavior, is there any way to have two rows in a UICollectionView which can scroll horizontally and simultaneously?
Should I consider using two collection views and have some logic that binds the scrolling of both the views? (I’d rather not have two UICollectionviews just to solve this problem)

Also, I am doing this through pure code. No storyboards or xib files have been used.
Can you try the code below?
I was able to do this i guess
class CollectionViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
setUpCollectionView()
}
fileprivate func setUpCollectionView() {
let collectionFlowLayout = UICollectionViewFlowLayout()
collectionFlowLayout.scrollDirection = .horizontal
collectionFlowLayout.itemSize = CGSize(width: 145, height: 145)
collectionView.setCollectionViewLayout(collectionFlowLayout, animated: true)
collectionView.delegate = self
collectionView.dataSource = self
}
}
extension CollectionViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCollectionViewCell", for: indexPath)
if indexPath.row % 2 == 0 {
cell.contentView.backgroundColor = UIColor.red
} else {
cell.contentView.backgroundColor = UIColor.green
}
return cell
}
}
NOTE: I have set collectionView height as 300
OUTPUT
Use 2 collectionView
let CellId1 = "CellId1"
lazy var collectionViewOne: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let width = collectionView.frame.width
let collectionViewOne = UICollectionView(frame: CGRect(x: 0, y: 100, width: width, height: 100), collectionViewLayout: layout)
collectionViewOne.showsHorizontalScrollIndicator = false
collectionViewOne.backgroundColor = .red
return collectionViewOne
}()
let CellId2 = "CellId2"
lazy var collectionViewTwo: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let collectionViewTwo = UICollectionView(frame: CGRect(x: 0, y: 250, width: width, height: 100), collectionViewLayout: layout)
collectionViewTwo.backgroundColor = .blue
return collectionViewTwo
}()
then for obtaining the numberOfItem and cellForRow:
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.collectionViewOne {
return 10
} else if collectionView == self.collectionViewTwo {
return 20
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.collectionViewOne {
let Cell1 = collectionViewOne.dequeueReusableCell(withReuseIdentifier: CellId1, for: indexPath)
Cell1.backgroundColor = .green
return Cell1
} else if collectionView == self.collectionViewTwo {
let Cell2 = collectionViewTwo.dequeueReusableCell(withReuseIdentifier: CellId2, for: indexPath )
Cell2.backgroundColor = .purple
return Cell2
}
}
and don't forget to register the collectionView in viewDidLoad()
collectionViewOne.delegate = self
collectionViewOne.dataSource = self
collectionViewTwo.delegate = self
collectionViewTwo.dataSource = self
collectionViewOne.register(Cell1.self, forCellWithReuseIdentifier: storyCell)
collectionViewTwo.register(Cell2.self, forCellWithReuseIdentifier: cardCell)

Auto sizing collection view cell size ignores auto layout on first display

Situlation
I made a horizontal scrolling collection view with UICollectionViewFlowLayout.automaticSize.
The collection view has height of 40
The cell has height of 40 which I set by autolayou on init.
Problem
The problem is that ONLY cells displayed on first displaying the collection view
has size of 50x50 which overridden autolayout height of 40 I manually set by code.
I checked the size on willDisplayCell delegate method.
When I scroll the collection view to display other cells, each newly displayed
cells have proper size with heigh of 40.
Although the cells with 50x50 size(as logged) are being displayed correctly(with height 40 it seems) on the screen.
From this, I get error messaged(logged) when I run the app on simulator and
the app crashed on if I run on my real device(iPhone8 iOS 11).
Code
ViewController
import UIKit
class HorizontalTabNavigationViewController: UIViewController {
// MARK: - Collection View
private lazy var navigationCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.backgroundColor = .white
cv.dataSource = self
cv.delegate = self
return cv
}()
private typealias Cell = HorizontalTabNavigationCell
private let cellId: String = "cell"
// MARK: - Property
var items: [HorizontalTabNavigationItem] = []
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.view.translatesAutoresizingMaskIntoConstraints = false
navigationCollectionView.register(Cell.self, forCellWithReuseIdentifier: cellId)
navigationCollectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
setupSubviews()
}
private func setupSubviews() {
view.addSubview( navigationCollectionView )
[
navigationCollectionView.topAnchor .constraint(equalTo: view.topAnchor),
navigationCollectionView.leftAnchor .constraint(equalTo: view.leftAnchor),
navigationCollectionView.rightAnchor .constraint(equalTo: view.rightAnchor),
navigationCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
].forEach { $0.isActive = true }
}
}
extension HorizontalTabNavigationViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! Cell
//cell.setProperties(item: items[indexPath.row])
//print("Cell for item: \(items[indexPath.row].title)")
return cell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
print("Cell Size on willDispaly cell")
print(cell.bounds.size)
print("\n")
}
}
extension HorizontalTabNavigationViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5 // Defines horizontal spacing between cells
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return .zero
}
}
Cell
class HorizontalTabNavigationCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupSelfHeight()
self.backgroundColor = .orange
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupSelfHeight() {
self.heightAnchor.constraint(equalToConstant: 40).isActive = true
self.widthAnchor.constraint(equalToConstant: 120).isActive = true
}
}
I solved the problem by setting custom size to flowlayout.
Inspired by Manav's anwer on this question below.
the behavior of the UICollectionViewFlowLayout is not defined, because the cell width is greater than collectionView width
private lazy var navigationCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
// [Here is the answer]
// Set Height equal or less than the height of the collection view.
// This is applied on first display of cells and
// will also not affect cells auto sizing.
layout.estimatedItemSize = CGSize(width: 200, height: 40) // UICollectionViewFlowLayoutAutomaticSize
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.showsHorizontalScrollIndicator = false
cv.backgroundColor = .white
cv.dataSource = self
cv.delegate = self
return cv
}()

UICollectionView is showing but the cells are not showing in swift

I created a collectionView programmatically (without storyboard). I set the background color to red, and it is showing properly. But the cells are not showing. Does anyone know why the cells are not showing?
private let cellId = "cellId"
class InfoViewShow: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
private let cellHeight:CGFloat = 80
var collectionView: UICollectionView?
let layout = UICollectionViewFlowLayout()
override init() {
super.init()
setupCollectionview()
collectionView?.registerClass(InfoCell.self, forCellWithReuseIdentifier: cellId)
}
private func setupCollectionview() {
if let view = UIApplication.sharedApplication().keyWindow {
//let y = (view.frame.width * 10 / 16) + 34 + 26
collectionView = UICollectionView(frame: .zero , collectionViewLayout: layout)
collectionView?.backgroundColor = UIColor.redColor()
collectionView?.delegate = self
collectionView?.dataSource = self
collectionView?.frame = CGRectMake(0, 0, view.frame.width, view.frame.height) // y to y
}
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellId, forIndexPath: indexPath)
cell.backgroundColor = UIColor.blackColor()
return cell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(collectionView.frame.width, cellHeight)
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(5, 5, 5, 5); //top,left,bottom,right
}
}
I dont' see you add collectionView to view.
if let view = UIApplication.sharedApplication().keyWindow {
//let y = (view.frame.width * 10 / 16) + 34 + 26
collectionView = UICollectionView(frame: .zero , collectionViewLayout: layout)
collectionView?.backgroundColor = UIColor.redColor()
collectionView?.delegate = self
collectionView?.dataSource = self
collectionView?.frame = CGRectMake(0, 0, view.frame.width, view.frame.height) // y to y
///try add
self.view.addSubview(collectionView)
}
You must implement numberOfSectionsInCollectionView of UICollectionViewDataSource delegate method.
This method is optional but you must implement it and return at least one section.
I know I am 6 years late to answer this question but I also struggled with it for 2 days before finding the mistake I was making. Hope this answer helps someone.
Check whether you have written the following two lines in your override func viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
self.collectionView.reloadData()

Stack UICollectionView sections

I have a problem. I'm using a UICollectionView to display two (they will be more) arrays of images, and every array has its own section. I'd like to achieve something like:
-----------------------
Section 1: Elements in array no. 1
-----------------------
Section 2: Elements in array no. 2
-----------------------
But as of now I get something like
-----------------------
Section 1: Elements in array no. 1 | Elements in array no. 2
-----------------------
How can I tell the UICollectionView to stack the two sections?
I thought about putting the collection view I have now into the cell of another collection view, but I have no idea how to do that.
Another solution may be to put them into the cell of a tableview, but it's best if I can avoid those workarounds.
I am not sure how to use it, but I think creating a custom layout may work, even if I need some help with that.
Also, I have been asked to not use any external library.
BTW I am programming in Objective-C.
You can make multiple section by simply implementing UICollectionViewDataSource method numberOfSectionsInCollectionView
class ViewController: UIViewController, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
let HEADER_IDENTIFIER = "header_identifier"
let CELL_IDENTIFIER = "cell_identifier"
lazy var collectionView:UICollectionView = {
var cv = UICollectionView(frame: self.view.bounds, collectionViewLayout: self.flowLayout)
cv.delegate = self
cv.dataSource = self
cv.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: self.CELL_IDENTIFIER)
cv.registerClass(UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: self.HEADER_IDENTIFIER)
cv.backgroundColor = UIColor(red: 230/255, green: 230/255, blue: 230/255, alpha: 1)
return cv
}()
lazy var flowLayout:UICollectionViewFlowLayout = {
var flow = UICollectionViewFlowLayout()
flow.sectionInset = UIEdgeInsetsMake(2.0, 2.0, 2.0, 2.0)
return flow
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.collectionView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: UICollectionView
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(CELL_IDENTIFIER, forIndexPath: indexPath)
cell.backgroundColor = UIColor.blueColor()
return cell
}
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: HEADER_IDENTIFIER, forIndexPath: indexPath)
header.backgroundColor = UIColor.redColor()
return header
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSize(width: 90, height: 90) // The size of one cell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSizeMake(self.view.frame.width, 1) // Header size
}
}

Resources