Creating a Nx2 collection view on iOS - 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)
}

Related

Dynamically resizing collection view cell

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.

UICollectionView reloadData() causes cells to overlap

Intro
Every time a user submits a comment, it is uploaded to the database. After that happened, the collectionView is reloaded. However, the reload does not have the expected behavior. Instead of adding the one comment to the end of the collectionView, it seems to randomly stack some other comment-cells and only display two comment cells, no matter how many comments there are.
This is the output of the View Debugger, you can clearly see that the collectionView cells are stacked:
At this point I would like to clarify that this is not a sizing error of the cells. They all have the correct size assigned, and if I call reloadData on the collectionView, at a later point in time, all the cells are displayed correctly.
I think things will become more clear if you take a look at the gif:
I really don't have any idea where this error comes from or how to fix it.
Code
Initialization of collectionView
private lazy var timelineCollectionView: UICollectionView = {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .vertical
flowLayout.estimatedItemSize = CGSize(width: self.view.frame.width, height: 10)
flowLayout.minimumInteritemSpacing = 0
flowLayout.minimumLineSpacing = 0
let cv = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
cv.contentInsetAdjustmentBehavior = .never
cv.backgroundColor = .clear
cv.scrollIndicatorInsets = UIEdgeInsets(top: self.coloredTitleBarHeight - self.getStatusBarHeight(), left: 0, bottom: 0, right: 0)
cv.delegate = self
cv.dataSource = self
cv.register(TLContentCell.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "content-header-cell")
cv.register(TLContentCell.self, forCellWithReuseIdentifier: "comment-cell")
return cv
}()
Code of the UICollectionViewDelegate methods:
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let cell = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "content-header-cell", for: indexPath) as! TLContentCell
cell.isHeaderCell = true
cell.timelineContent = tlContentItem
cell.delegate = self
cell.isUserInteractionEnabled = true
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return commentItems.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "comment-cell", for: indexPath) as! TLContentCell
cell.delegate = self
cell.timelineContent = TLContent(nativeContentItem: commentItems[indexPath.row])
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
let indexPath = IndexPath(row: 0, section: section)
let headerView = self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionHeader, at: indexPath) as! TLContentCell
return headerView.contentView.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height),
withHorizontalFittingPriority: .required, // Width is fixed
verticalFittingPriority: .fittingSizeLevel) // Height can be as large as needed
}
The code of the comment-upload handler function:
#objc func submitComment() {
submitCommentButton.isEnabled = false
submitCommentButton.backgroundColor = ColorCodes.lightGray
guard let user = Auth.auth().currentUser else {
print("No user detected. Redirecting to the login screen!")
self.displayWelcomeScreen()
return
}
guard commentTextView.text.count > 0, let commentContent = commentTextView.text else { print("Submission of comment aborted due to empty content."); return }
commentTextView.resignFirstResponder()
commentTextView.text = ""
calculateTextViewSize()
ContentUploader.uploadComment(uid: user.uid, content: NativeContentBase(msg: commentContent, usersTagged: [], topicsTagged: [], mediaAssigned: []), referencedContent: tlContentItem.nativeContent.contentId) { (error, comment) in
if let err = error {
print("An error occured during the comment upload: ", err)
return
}
print("Successfully uploaded comment!")
self.commentItems.append(comment)
DispatchQueue.main.async {
print(self.commentItems.count)
self.timelineCollectionView.reloadData()
}
}
print("Comment scheduled for submission!")
}
I also made sure that I am in the main thread when calling the reloadData() functionality. However, the weird behavior persists.
In order to adjust the collectionView's contentInset and scrollIndicatorInsets to the display of the keyboard, I am using these two functions:
private func adjustScrollBehavior() {
let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
let bottomSafeAreaHeight = window?.safeAreaInsets.bottom ?? 0
// adjust the content Inset of the timelineCollectionView
let heightOfCommentSection: CGFloat = commentTextViewHeightAnchor.constant + abs(commentTextViewBottomAnchor.constant) + 8 // get the absolute value of the bottomAnchor's constant as it would be negative
timelineCollectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: heightOfCommentSection + bottomSafeAreaHeight, right: 0) // does not automatically take care of safe area insets
timelineCollectionView.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: heightOfCommentSection, right: 0) // automatically takes care of safe area insets
}
func calculateTextViewSize() {
let minimumSize = commentTextView.layoutMargins.top + commentTextView.layoutMargins.bottom + commentTextView.font!.lineHeight
let fixedWidth = self.view.frame.width - 32
let maximumSize: CGFloat = 155
let newSize = commentTextView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
print(newSize)
if newSize.height > minimumSize && newSize.height <= maximumSize {
commentTextViewHeightAnchor.constant = newSize.height
} else {
commentTextViewHeightAnchor.constant = newSize.height > minimumSize ? maximumSize : minimumSize
}
adjustScrollBehavior()
}
Closing
I know this is a lot of code, but basically, that's everything I use. I have no idea why this happens, and I would be incredibly grateful for any help I could get.
Thanks a lot for your help :)

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.

UICollectionViewFlowLayout Subclass causes some cells to not appear

I have a vertically scrolling UICollectionView that uses a subclass of UICollectionViewFlowLayout to try and eliminate inter-item spacing. This would result in something that looks similar to a UITableView, but I need the CollectionView for other purposes. There is a problem in my implementation of the FlowLayout subclass that causes cells to disappear when scrolling fast. Here is the code for my FlowLayout subclass:
EDIT: See Comments For Update
class ListLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
if var answer = super.layoutAttributesForElementsInRect(rect) {
for attr in (answer as [UICollectionViewLayoutAttributes]) {
let ip = attr.indexPath
attr.frame = self.layoutAttributesForItemAtIndexPath(ip).frame
}
return answer;
}
return nil
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
let currentItemAtts = super.layoutAttributesForItemAtIndexPath(indexPath) as UICollectionViewLayoutAttributes
if indexPath.item == 0 {
var frame = currentItemAtts.frame
frame.origin.y = 0
currentItemAtts.frame = frame
return currentItemAtts
}
let prevIP = NSIndexPath(forItem: indexPath.item - 1, inSection: indexPath.section)
let prevFrame = self.layoutAttributesForItemAtIndexPath(prevIP).frame
let prevFrameTopPoint = prevFrame.origin.y + prevFrame.size.height
var frame = currentItemAtts.frame
frame.origin.y = prevFrameTopPoint
currentItemAtts.frame = frame
return currentItemAtts
}
}
One other thing to note: My cells are variable height. Their height is set by overriding preferredLayoutAttributesFittingAttributes in the subclass of the custom cell:
override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes! {
let attr: UICollectionViewLayoutAttributes = layoutAttributes.copy() as UICollectionViewLayoutAttributes
attr.frame.size = CGSizeMake(self.frame.size.width, myHeight)
return attr
}
And I set the layout's estimated size on initialization:
flowLayout.estimatedItemSize = CGSize(width: UIScreen.mainScreen().bounds.size.width, height: 60)
Here is a GIF that demonstrates this problem:
Does anybody have an idea as to what's going on? Your help is much appreciated.
Thanks!

Resources