Dynamically resizing collection view cell - ios

I have this older code example that I updated but I am still a newbie and can't figure out how to fix this one error:
In the class that holds the collection view:
var array = ["Just testing this out"]
Now in the UICollectionViewDelegateFlowLayout extension:
private func estimateFrameForText(text: String) -> CGRect {
//we make the height arbitrarily large so we don't undershoot height in calculation
let height: CGFloat = 1000
let size = CGSize(width: 398, height: height)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17, weight: UIFont.Weight.light)]
return NSString(string: text).boundingRect(with: size, options: options, attributes: attributes, context: nil)
}
Also:
private func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var height: CGFloat = 295
//we are just measuring height so we add a padding constant to give the label some room to breathe!
var padding: CGFloat = 14
//estimate each cell's height
if let text = array[indexPath.item].text {
height = estimateFrameForText(text).height + padding
}
return CGSize(width: 398, height: height) }
This is where I get the error:
if let text = array[indexPath.item].text
And here's the UILabel from the collection view cell class I have no idea where to implement:
#IBOutlet weak var TextPosted: UILabel!

Your test array needs some adjusting. Try declaring it as an optional (since it likely will be with actual data loaded from somewhere).
var array: [String]? = ["Just testing this out", "Another test"]
Then this:
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return array?.count ?? 0
}
Then update your if let statement in your collecitonView sizeForItemAtIndexPath function:
if let text = array?[indexPath.item]{
}
On a side note, I wrote an open-source extension to measure string size for things like this a few days ago.
Hope this helps.

Related

Dynamically change UITableViewCell height - Swift 4

How do I go about dynamically changing the UITableViewCell height? I've tried implementing the following, but for some reason, it isn't working. The code crashes as soon as I load the view controller displaying this table view
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cell = tableView.cellForRow(at: indexPath) as! AvailableRideCell
return cell.getHeight()
}
This is the getHeight function in my AvailableRideCell
func getHeight() -> CGFloat {
return self.destinationLabel.optimalHeight + 8 + self.originLabel.optimalHeight + 8 + self.priceLabel.optimalHeight
}
And this is the optimalHeight function
extension UILabel {
var optimalHeight : CGFloat {
get {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = self.font
label.text = self.text
label.sizeToFit()
return label.frame.height
}
}
}
Keep in mind that UITableViewCell is reused. So getting the height of the current cell can be unstable.
A better way is to have one fake/placeholder cell (I call the calculator cell) and use that to calculate the size of the cell.
So in the heightForRowAt method, you get the data instead of the cell.
Put that data inside the calculator cell and get the height from there.
You code crashes because of this line
let cell = tableView.cellForRow(at: indexPath) as! AvailableRideCell
From Apple Documentation we know that this method is used for optimization. The whole idea is to get cells heights without wasting time to create the cells itself. So this method called before initializing any cell, and tableView.cellForRow(at: indexPath) returns nil. Because there are no any cells yet. But you're making force unwrapping with as! AvailableRideCell and your code crashed.
So at first, you need to understand, why you should not use any cells inside the cellForRow(at ) method.
After that, you need to change the logic so you could compute content height without calling a cell.
For example, in my projects, I've used this solution
String implementation
extension String {
func height(for width: CGFloat, font: UIFont) -> CGFloat {
let maxSize = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
let actualSize = self.boundingRect(with: maxSize,
options: [.usesLineFragmentOrigin],
attributes: [NSAttributedStringKey.font: font],
context: nil)
return actualSize.height
}
}
UILabel implementation
extension String {
func height(for width: CGFloat, font: UIFont) -> CGFloat {
let labelFrame = CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude)
let label = UILabel(frame: labelFrame)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.font = font
label.text = self
label.sizeToFit()
return label.frame.height
}
}
With that, all you need to do is to compute your label and store its font.
var titles = ["dog", "cat", "cow"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// number of rows is equal to the count of elements in array
return titles.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cellTitle = titles[indexPath.row]
return cellTitle.height(forWidth: labelWidth, font: labelFont)
}
Dynamic rows height changing
If you'll need to update row height, all you need to do is to call this method when your content had been changed. indexPath is the index path of changed item.
tableView.reloadRows(at: [indexPath], with: .automatic)
Hope it helps you.
You don't mention if you are using Auto Layout, but if you are, you can let Auto Layout manage the height of each row. You don't need to implement heightForRow, instead set:
tableView.rowHeight = UITableViewAutomaticDimension
And configure your UILabel with constraints that pin it to the cell's content view:
let margins = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
cellLabel.leadingAnchor.constraint(equalTo: margins.leadingAnchor),
cellLabel.trailingAnchor.constraint(equalTo: margins.trailingAnchor),
cellLabel.topAnchor.constraint(equalTo: margins.topAnchor),
cellLabel.bottomAnchor.constraint(equalTo: margins.bottomAnchor)
])
Each row will expand or contract to fit the label's intrinsic content size. The height is automatically adjusted if the device landscape/portrait orientation changes, without re-loading the cell. If you want the row height to change automatically when the device's UIContentSizeCategory changes, set the following:
cellLabel.adjustsFontForContentSizeCategory = true

UICollectionView not rendering all rows

I'm trying to create a simple app, where one can enter a number of columns and a number of rows for an UICollectionView. The collection view then calculates the size of possible squares that fit into it and draws them.
I want to allow a maximum of 32 in width and 64 in height. Scrolling is disabled as the whole grid should be shown at once.
For example, 4x8 looks like this
and 8x4 will look like this
So as one can see that works fine. The problems comes with a higher amount of columns and/or rows. Up to 30x8 everything is fine but starting with 31 only 6 of the 8 rows are drawn.
So I don't understand why. Following is the code I use to calculate everything:
Number of section and number of rows:
func numberOfSections(in collectionView: UICollectionView) -> Int
{
let num = Int(heightInput.text!)
if(num != nil)
{
if(num! > 64)
{
return 64
}
return num!
}
return 8
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
let num = Int(widthInput.text!)
if(num != nil)
{
if(num! > 32)
{
return 32
}
return num!
}
return 4
}
Cell for item at indexPath
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let size = calculateCellSize()
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
var origin = cell.frame.origin
origin.x = 1+CGFloat(indexPath.row) + size.width*CGFloat(indexPath.row)
origin.y = 1+CGFloat(indexPath.section) + size.height*CGFloat(indexPath.section)
cell.frame = CGRect(origin: origin, size: size)
NSLog("Cell X:%#, Cell Y:%#",origin.x.description,origin.y.description)
return cell
}
The calculate size method
func calculateCellSize() -> CGSize
{
//First check if we have valid values
let col = Int(widthInput.text!)
let row = Int(heightInput.text!)
if(col == nil || row == nil)
{
return CGSize(width: 48.0, height: 48.0)
}
//If there are more or equal amount of columns than rows
let columns = CGFloat(col!)
let rows = CGFloat(row!)
if(columns >= rows)
{
//Take the grid width
let gridWidth = drawCollection.bounds.size.width
//Calculate the width of the "pixels" that fit the width of the grid
var pixelWidth = gridWidth/columns
//Remember to substract the inset from the width
let drawLayout = drawCollection.collectionViewLayout as? UICollectionViewFlowLayout
pixelWidth -= (drawLayout?.sectionInset.left)! + 1/columns
return CGSize(width: pixelWidth, height: pixelWidth)
}
else
{
//Rows are more than columns
//Take the grid height as reference here
let gridHeight = drawCollection.bounds.size.height
//Calculate the height of the "pixels" that fit the height of the grid
var pixelHeight = gridHeight/rows
//Remember to substract the inset from the height
let drawLayout = drawCollection.collectionViewLayout as? UICollectionViewFlowLayout
pixelHeight -= (drawLayout?.sectionInset.top)! + 1/rows
return CGSize(width: pixelHeight, height: pixelHeight)
}
return CGSize(width: 48.0, height: 48.0)
}
For debugging reasons I put a counter into the cellforItemAtIndexPath method and in fact I can see that the last two rows are not called. The counter ends at 185 but in theory it should have been called 248 times and in fact the difference will show it is 2*32 - 1(for the uneven 31) so the last missing rows....
Several things came to my mind what the reason is but nothing of it seems to be:
the cells are not drawn at the right location (aka outside the grid) -> At least not correct as the method is only called 185 times.
The cells are calculated to be outside the grid therefore not tried to be rendered by the UICollectionView -> Still possible as I couldn't figure how to proof that.
There is a (if so hopefully configurable) maximum amount of elements the UICollectionView can draw and 31x8 already exceeds that number -> Still possible couldn't find anything about that.
So summary:
Is it possible to display all elements in the grid (32x64 max) and if so, what is wrong in my implementation?
Thank you all for your time and answers!
You're doing a whole lot of calculating that you don't need to do. Also, setting the .frame of a cell is a really bad idea. One big point of a collection view is to avoid having to set frames.
Take a look at this:
class GridCollectionViewController: UIViewController, UICollectionViewDataSource {
#IBOutlet weak var theCV: UICollectionView!
var numCols = 0
var numRows = 0
func updateCV() -> Void {
// subtract the number of colums (for the 1-pt spacing between cells), and divide by number of columns
let w = (theCV.frame.size.width - CGFloat((numCols - 1))) / CGFloat(numCols)
// subtract the number of rows (for the 1-pt spacing between rows), and divide by number of rows
let h = (theCV.frame.size.height - CGFloat((numRows - 1))) / CGFloat(numRows)
// get the smaller of the two values
let wh = min(w, h)
// set the cell size
if let layout = theCV.collectionViewLayout as? UICollectionViewFlowLayout {
layout.itemSize = CGSize(width: wh, height: wh)
}
// reload the collection view
theCV.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// start with a 31x20 grid, just to see it
numCols = 31
numRows = 20
updateCV()
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return numRows
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return numCols
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = theCV.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
return cell
}
#IBAction func btnTap(_ sender: Any) {
// example of changing the number of rows/columns based on user action
numCols = 32
numRows = 64
updateCV()
}
}
You need to develop your own UICollectionViewLayout. With this approach you can achieve any result human being can imagine.
import UIKit
class CollectionLayout: UICollectionViewFlowLayout {
private var width: Int = 1
private var height: Int = 1
func set(width: Int, height: Int) {
guard width > 0, height > 0 else { return }
self.height = height
self.width = width
calculateItemSize()
}
private func calculateItemSize() {
guard let collectionView = collectionView else { return }
let size = collectionView.frame.size
var itemWidth = size.width / CGFloat(width)
// spacing is needed only if there're more than 2 items in a row
if width > 1 {
itemWidth -= minimumInteritemSpacing
}
var itemHeight = size.height / CGFloat(height)
if height > 1 {
itemHeight -= minimumLineSpacing
}
let edgeLength = min(itemWidth, itemHeight)
itemSize = CGSize(width: edgeLength, height: edgeLength)
}
// calculate origin for every item
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = super.layoutAttributesForItem(at: indexPath)
// calculate item position in the grid
let col = CGFloat(indexPath.row % width)
let row = CGFloat(Int(indexPath.row / width))
// don't forget to take into account 'minimumInteritemSpacing' and 'minimumLineSpacing'
let x = col * itemSize.width + col * minimumInteritemSpacing
let y = row * itemSize.height + row * minimumLineSpacing
// set new origin
attributes?.frame.origin = CGPoint(x: x, y: y)
return attributes
}
// accumulate all attributes
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil }
var newAttributes = [UICollectionViewLayoutAttributes]()
for attribute in attributes {
if let newAttribute = layoutAttributesForItem(at: attribute.indexPath) {
newAttributes.append(newAttribute)
}
}
return newAttributes
}
}
Set our layout to UICollectionView
Update collection view every time user enters width and height:
#IBAction func onSetTapped(_ sender: Any) {
width = Int(widthTextField.text!)
height = Int(heightTextField.text!)
if let width = width, let height = height,
let layout = collectionView.collectionViewLayout as? CollectionLayout {
layout.set(width: width, height: height)
collectionView.reloadData()
}
}

UICollectionView that does not scroll

I'm trying to create a UICollectionView with all cells 100% visible, such that I will not need scrolling to see them all. I'm currently trying to get a 3x3 grid displayed, and calculating the size of the cells on the fly.
I have the CollectionView and a UIView for a header in a Container View. The header is pinned to the top of the container with a height of 100px. The CollectionView is below that, pinned to each side, the bottom, and has its top pinned to the bottom of the header.
When I use sizeForItemAt, I'm trying to find the size of the visible area to split it up into 1/3 sized chunks (padding/insets aside). My code looks like:
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let numRows = self.numRows()
let itemsPerRow = self.itemsPerRow()
// let frameSize = collectionView.frame.size
let frameSize = collectionView.bounds.size
// let frameSize = collectionView.collectionViewLayout.collectionViewContentSize
// let frameSize = collectionView.intrinsicContentSize
let totalItemPadding = self.itemPadding * (itemsPerRow - 1)
let totalLinePadding = self.linePadding * (numRows - 1)
let availableWidth = frameSize.width - totalItemPadding
var widthPerItem = availableWidth / itemsPerRow
let availableHeight = frameSize.height - totalLinePadding
var heightPerItem = availableHeight / numRows
return CGSize(width: widthPerItem, height: heightPerItem)
}
The result is always that the 3rd row is about half-obscured, as it looks like the frameSize is "taller" than it actually displays in the simulator.
Is there something in UICollectionView that would give me the visible size? Am I at a wrong time in terms of layout timing, or should I add another method that invalidates size at some point?
I haven't found any tutorials out there for a collection view that shows all items, and does not vertically scroll, so any other pointers (or even libraries that do something like this) would be greatly appreciated.
Thanks!
Are you sure this method is getting called? Either add a log statement or a breakpoint in this routine and make sure it's getting called.
A common problem that would prevent this from getting called would be if you neglected formally declare your view controller to conform to UICollectionViewDelegateFlowLayout. In that case, it would use whatever it found in the storyboard. But when I did this, your code worked fine for me, for example:
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let numRows = self.numRows()
let itemsPerRow = self.itemsPerRow()
let frameSize = collectionView.bounds.size
let layout = collectionViewLayout as! UICollectionViewFlowLayout
let totalItemPadding = layout.minimumInteritemSpacing * (itemsPerRow - 1)
let totalLinePadding = layout.minimumInteritemSpacing * (numRows - 1)
let availableWidth = frameSize.width - totalItemPadding
let widthPerItem = availableWidth / itemsPerRow
let availableHeight = frameSize.height - totalLinePadding
let heightPerItem = availableHeight / numRows
return CGSize(width: widthPerItem, height: heightPerItem)
}
}
Note, I also used minimumInteritemSpacing, so I use the existing spacing parameter rather than defining your own. It strikes me as better to use an existing parameter (esp one that you can also set in IB).
By the way, the alternative, if it's always going to be on a single screen, is to use your own custom layout, rather than flow layout. That way you don't entangle the collection view's delegate with lots of cumbersome code. It would be a little more reusable. For example:
class GridLayout: UICollectionViewLayout {
var itemSpacing: CGFloat = 5
var rowSpacing: CGFloat = 5
private var itemSize: CGSize!
private var numberOfRows: Int!
private var numberOfColumns: Int!
override func prepare() {
super.prepare()
let count = collectionView!.numberOfItems(inSection: 0)
numberOfColumns = Int(ceil(sqrt(Double(count))))
numberOfRows = Int(ceil(Double(count) / Double(numberOfColumns)))
let width = (collectionView!.bounds.width - (itemSpacing * CGFloat(numberOfColumns - 1))) / CGFloat(numberOfColumns)
let height = (collectionView!.bounds.height - (rowSpacing * CGFloat(numberOfRows - 1))) / CGFloat(numberOfRows)
itemSize = CGSize(width: width, height: height)
}
override var collectionViewContentSize: CGSize {
return collectionView!.bounds.size
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.center = centerForItem(at: indexPath)
attributes.size = itemSize
return attributes
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return (0 ..< collectionView!.numberOfItems(inSection: 0)).map { IndexPath(item: $0, section: 0) }
.flatMap { layoutAttributesForItem(at: $0) }
}
private func centerForItem(at indexPath: IndexPath) -> CGPoint {
let row = indexPath.item / numberOfColumns
let col = indexPath.item - row * numberOfColumns
return CGPoint(x: CGFloat(col) * (itemSize.width + itemSpacing) + itemSize.width / 2,
y: CGFloat(row) * (itemSize.height + rowSpacing) + itemSize.height / 2)
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
And then, in the view controller:
override func viewDidLoad() {
super.viewDidLoad()
let layout = GridLayout()
layout.itemSpacing = 10
layout.rowSpacing = 5
collectionView?.collectionViewLayout = layout
}
For the layout size, you could use UICollectionViewFlowLayout and relate the dimensions in terms of your screen width and height so as to preserve the grid style look of the UICollectionView. See: https://developer.apple.com/documentation/uikit/uicollectionviewflowlayout and https://www.raywenderlich.com/136159/uicollectionview-tutorial-getting-started.
As for your scrolling issue, check that your scrolling is enabled, your constraints are setup correctly, and you don't have the problem mentioned at this link. UICollectionView does not scroll
Good luck! :)
Your scrolling is not working because the view is just the size of the screen...
Just to get the trick add an offset of 1000. The amount of the total height of the collectionView is every row * (row.size.height + padding).
In my case this was a lot simpler, the basic idea was to add extra padding to the end of collection view. On my case my orientation was horizontal but the same should apply to a vertical one (have not tested that one) The trick was to adjust the delegate function as follow:
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets.init(top: collectionView.bounds.width * 0.02, left: collectionView.bounds.width * 0.02, bottom: collectionView.bounds.width * 0.02, right: collectionView.bounds.width * 0.22)
}
}
Noticed the difference on the right value, in your case it would be the bottom value of the row.

How to insert Item to a collectionView ? Swift3

I am creating a chatting app in Xcode 8 with Swift 3 and I am using collectionView for the messages. but I am having an issue with inserting a new item to the collection view when press send:
msg.append(message) // msg : Messages Array
let item = msg.count - 1
let IndexPath = NSIndexPath(item: item, section: 0)
self.collectionView.insertItems(at: [IndexPath])
error show up ( Ambiguous reference to member 'collectionView(_:numberOfItemsInSection:)'
How to solve this issue. and is working with storyboard effecting the work with collectionView because I cannot change the cell size with :
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if let messageText = msg[indexPath.item].text {
let size = CGSize(width: 250 , height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let esf = NSString(string: messageText).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: CGFloat(18))], context: nil)
print("Changed")
return CGSize(dictionaryRepresentation: esf as! CFDictionary)!
}
print("Not Changed")
return CGSize(width: view.frame.width, height: CGFloat(100))
}
I have but a break point and it has not been reached!
You are using UIViewController and not UICollectionViewController so you don't get the collectionView property for free. You need to drag in an outlet form your storyboard and name it #IBOutlet var collectionView: UICollectionView!.
in my case i reference
self.tableView.SOMEMETHOD
but there is no outlet exist with name "tableView".
please review your code, i think it's issue of outlet

Creating a Nx2 collection view on iOS

I'm trying to create an Nx2 (n rows, 2 columns) collection view where the cells size dynamically adjusts to perfectly fit the screen.
So I have the following controller in my storyboard:
This are the constraints for the Collection View:
And this is the respective View Controller code:
import UIKit
class SREventAttendeesCollectionViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var screenSize: CGRect!
var screenWidth: CGFloat!
var screenHeight: CGFloat!
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
screenSize = UIScreen.mainScreen().bounds
screenWidth = screenSize.width
screenHeight = screenSize.height
let collectionViewWidth = (self.collectionView.frame.size.width/2)
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: collectionViewWidth, height: screenHeight/3)
collectionView.setCollectionViewLayout(layout, animated: true)
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionCell", forIndexPath: indexPath) as! SRCollectionViewCell
// Configure the cell
let collectionViewWidth = (self.collectionView.frame.size.width/2)
print(collectionViewWidth)
cell.frame.size.width = collectionViewWidth
cell.nameAgeLabel.frame.size.width = collectionViewWidth
//cell.pictureImageView.image = myImage
return cell
}
}
Sadly this is resulting on the following:
Does anyone have any idea what I'm missing here?
Sure, firstly there are several things wrong with your code in terms of the collection view flow layout.
1) You don't need to specify a type of UICollectionViewFlowLayout like in Objective C. Swift can match this automatically. Instead you should downcast to the required type (I'll get to this later)
2) I wouldn't create a new layout object and assign it. Instead I would just effect the current layout object (see my example code below)
3) Your not taking into account the minimumInterItemSpacing value which is why your cells are not fitting 2 per row.
I would make several changes to the way your collection view flow layout code is implemented. This is how I would achieve this (I've not compiled this so there might be some minor errors):
let screenWidth = collectionView.bounds.size.width;
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.sectionInset = UIEdgeInsetsMake(0,0,0,0)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
let avaliableWidth = screenWidth - (layout.sectionInset.left + layout.sectionInset.right + layout.minimumInteritemSpacing)
let itemWidth = avaliableWidth / 2
// You can set the value of 50 to whatever your height is
layout.itemSize = CGMake(itemWidth, 50)
}

Resources