Issue with custom UICollectionViewLayout leaving gap at bottom of cell - swift

I have a UICollectionView with a custom layout. As seen in the image below there is always a gap between the content and the bottom of the cell.
Each cell consists of an image and three labels. I've printed out the size of the of the cell and also printed out the size of the image + the 3 lables and the match, so I have no idea where this extra space is coming from.
let width = columnWidth - cellPadding*2
let photoHeight = delegate.collectionView(collectionView!, heightForPhotoAtIndexPath: indexPath , withWidth:width)
print("photo height is: \(photoHeight)")//photo height is: 95.4078125
let label1Height = delegate.collectionView(collectionView!, heightForLabel1AtIndexPath: indexPath, withWidth: width)
print("label1Height is \(label1Height)") // label1Height is 39.0
let label2Height = delegate.collectionView(collectionView!,heightForLabel2AtIndexPath: indexPath, withWidth: width) //label2Height is 53.0
print("label2Height is \(label2Height)")// label2Height is 53.0
let label3Height = delegate.collectionView(collectionView!, heightForLabel3AtIndexPath: indexPath, withWidth: width)
print("label3Height is \(label3Height)") //label3Height is 135.0
let height = photoHeight + label1Height + label2Height + label3Height
print("total height is: \(height)") //total height is: 322.4078125
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
I've pinned the bottom of label3 to the bottom of the cell +4, but that doesn't help

Related

How to set Size for a specific cell when using dynamic size uicollectionView cell

I'm following this tutorial:
https://www.raywenderlich.com/4829472-uicollectionview-custom-layout-tutorial-pinterest
To achieve this layout
Basically a horizontal collectionView in section 1 and a vertical collectionView in section 2
My thought was putting a collectionView inside the first cell, which should have the size: CGSize(width: screen.width, height: 100)
But since I'm using the method from the link, I don't know how to give a specific size for the first cell. This is what I've tried:
Disclaimer: As OP had asked a question earlier on how to create a Pinterest layout and I had suggested Raywenderlich tutorial in comments, I believe this question from OP is extension of that
Answer:
Looking at the design you added, I believe you are trying to add a CollectionView with a horizontal scroll as the first cell which spans the entire collection view width. Because you have subclassed the UICollectionViewFlowLayout you need to handle this special case in prepare method itself
override func prepare() {
// 1
guard
cache.isEmpty,
let collectionView = collectionView
else {
return
}
// 2
let columnWidth = contentWidth / CGFloat(numberOfColumns)
var xOffset: [CGFloat] = []
for column in 0..<numberOfColumns {
xOffset.append(CGFloat(column) * columnWidth)
}
var column = 0
var yOffset: [CGFloat] = .init(repeating: 0, count: numberOfColumns)
// 3
for item in 0..<collectionView.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)
let attributes: UICollectionViewLayoutAttributes
let photoHeight = delegate?.collectionView(
collectionView,
heightForPhotoAtIndexPath: indexPath) ?? 180
let height = cellPadding * 2 + photoHeight
let frame: CGRect
if item == 0 {
frame = CGRect(x: 0,
y: yOffset[column],
width: columnWidth,
height: height)
contentHeight = frame.maxY
for column in 0..<numberOfColumns {
yOffset[column] = frame.maxY
}
column = 0
}
else {
// 4
frame = CGRect(x: xOffset[column],
y: yOffset[column],
width: columnWidth,
height: height)
contentHeight = max(contentHeight, frame.maxY)
yOffset[column] = yOffset[column] + height
column = column < (numberOfColumns - 1) ? (column + 1) : 0
}
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
cache.append(attributes)
}
}
In your for item in 0..<collectionView.numberOfItems(inSection: 0) { I have added a special condition for handling if item == 0 { where in instead of using column width, I have used content width (as per your design you want first cell to cover the whole width of collection view)
Finally in your delegate method
extension PhotoStreamViewController: PinterestLayoutDelegate {
func collectionView(
_ collectionView: UICollectionView,
heightForPhotoAtIndexPath indexPath:IndexPath) -> CGFloat {
if indexPath.row == 0 {
return 180 // whatever is the height of first cell
}
return photos[indexPath.item].image.size.height
}
}
A word of caution
In your design I can see that you have added two section headers Header 1 and Header 2 I guess in that case your returning number of sections as at least 2, but the tutorial focuses on a single section collection view, if you notice
for item in 0..<collectionView.numberOfItems(inSection: 0) {
section 0 is hard coded, you might have to handle it dynamically if you wanna handle multiple sections and might need a lot of reworking around prepare
A strategy you might follow to gain the ability to control the size of your collection items is to have your delegate for the UICollection view derive from UICollectionViewDelegateFlowLayout instead of just from UICollectionViewDelegate. By deriving from UICollectionViewDelegateFlowLayout your delegate can implement:
func collectionView(UICollectionView, layout: UICollectionViewLayout, sizeForItemAt: IndexPath) -> CGSize
And tell the system the size of the first item.

How can I get frame of superview's frame in swift?

I want to create multiple buttons and position it inside uiview and fit to uiview.(as picture)
I need to get uiview frame to calculate and divide as I need , to set button's width and height depending on device size.
for row in 0 ..< 4 {
for col in 0..<3 {
let numberButton = UIButton()
numberButton.frame = CGRect(x: Int(buttonView.frame.width / 3 - 20) * col, y: row * (320 / 4), width: Int(buttonView.frame.width) / 3, height: Int(buttonView.frame.height) / 4)
numberButton.setTitle("button", for: .normal)
numberButton.titleLabel?.font = numberButton.titleLabel?.font.withSize(30)
numberButton.setTitleColor(UIColor.black)
buttonView.addSubview(numberButton)
}
}
I tried like code above, but buttonView.frame.width returns nil.
How can I calculate this view's frame?
You can use UIStackViews to achieve this grid layout. This way, you don't have to calculate the frames of each button. Doing so is bad practice anyway. You should instead use AutoLayout constraints to layout your views. Here's a tutorial to get you started.
Anyway, here's how you would use UIStackViews to create a grid of buttons:
// here I hardcoded the frame of the button view, but in reality you should add
// AutoLayout constraints to it to specify its frame
let buttonView = UIStackView(frame: CGRect(x: 0, y: 0, width: 600, height: 320))
buttonView.alignment = .fill
buttonView.axis = .vertical
buttonView.distribution = .fillEqually
buttonView.spacing = 20 // this is the spacing between each row of buttons
for _ in 0..<4 {
var buttons = [UIButton]()
for _ in 0..<3 {
let numberButton = UIButton(type: .system)
numberButton.setTitle("button", for: .normal)
numberButton.titleLabel?.font = numberButton.titleLabel?.font.withSize(30)
numberButton.setTitleColor(UIColor.black, for: .normal)
// customise your button more if you want...
buttons.append(numberButton)
}
let horizontalStackView = UIStackView(arrangedSubviews: buttons)
horizontalStackView.alignment = .fill
horizontalStackView.axis = .horizontal
horizontalStackView.distribution = .fillEqually
horizontalStackView.spacing = 20 // this is the spacing between each column of buttons
buttonView.addArrangedSubview(horizontalStackView)
}
Result from playground quick look:

Adding UIButtons dynamically in UICollectionViewCell - Swift

Trying to show buttons dynamically in a UICollectionViewCell depends on data coming in array from api. Below is what trying to achieve:
This is so far what i have achieved using button, but i need multiple buttons means button next to "About us" then next until space there then move to next line and setting height of cell as per height of view in cell. Please guide how it can be achieved.
Below is my code:
var xPosition = cell.btnOptions.frame.origin.x
var yPosition = cell.btnOptions.frame.origin.y
var width = cell.btnOptions.frame.size.width
var height = cell.btnOptions.frame.size.height
if((arrOptions?.count ?? 0) > 0)
{
for i in 0..<arrOptions!.count
{
//
cell.btnOptions.tag = i
cell.btnOptions.layer.cornerRadius = 3.0
cell.btnOptions.frame = CGRect.init(x: xPosition, y: yPosition, width: width, height: height)
//
cell.btnOptions.setTitle(arrOptions?.objectAt(i).object(forKey: "title") as? String, for: .normal)
xPosition = width + width + 8
yPosition = height
}

Custom UICollectionViewCell size in Swift 4.2

I am working on a crossword app but I keep missing something when I calculate each itemSize of the UICollectionView. I get the right size for iPhone SE but the wrong size for iPhone XR. What am I doing wrong?
func setLayout() -> UICollectionViewFlowLayout {
let layout = UICollectionViewFlowLayout()
layout.itemSize = getCellSize()
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
return layout
}
func getCellSize() -> CGSize {
let width:CGFloat = self.view.frame.width/CGFloat(crossWordData[0].count)
let height:CGFloat = (self.view.frame.height - TitleView.frame.height)/CGFloat(crossWordData.count) - cellsBorderWidth
//cellsBorderWidth = 1.2
let cellSize = CGSize(width:width , height:height)
return cellSize
}
This is because self.view ends where is bottom of the display. New iPhones (X, Xs, Xs Max, Xr) have on the bottom this place for "home gesture". So for your calculation, content of your CollectionView is bigger than CollectionView because your CollectionView has bottom constraint equal to safe area bottom constraint which is for new iPhones higher than where bottom of the display is. So when you need width and height instead of this
let width:CGFloat = self.view.frame.width/CGFloat(crossWordData[0].count)
let height:CGFloat = (self.view.frame.height - TitleView.frame.height)/CGFloat(crossWordData.count) - cellsBorderWidth
call this
let width:CGFloat = self.yourCollectionView.frame.width/CGFloat(crossWordData[0].count)
let height:CGFloat = self.yourCollectionView.frame.height/CGFloat(crossWordData.count) - cellsBorderWidth
Eventually this is how I changed my code -
let width:CGFloat = self.view.frame.width/CGFloat(crossWordData[0].count)
let height:CGFloat = self.view.frame.height*0.85/CGFloat(crossWordData.count)
The collections view height is 85% in the screen layout

UITableview with dynamic height cells & filling footer view

I am trying to make a UITableview with a footer view that fills the remaining empty space of the UITableview. However, the catch is that the cells have varying heights.
I am aware that, if the cells were all a static height, I could simply count how many cells there are and multiply that by the static height. Unfortunately, that won't work in this case.
Current solution:
Works for static height cells.
Does not work for dynamic height cells.
private func adjustFooterHeight() {
if data.count == 0 {
footerView.frame.size.height = tableView.bounds.height
}
else {
//I use data.count instead of (data.count - 1) as I have the extra hidden cell, as pictured in image 2.
let bottomFrame = tableView.rectForRow(at: IndexPath(row: data.count, section: 0))
let height = tableView.bounds.height - (bottomFrame.origin.y + bottomFrame.height - rowHeight)
footerView.frame.size.height = height < 0 ? 0 : height
tableView.reloadData()
}
}
Diagram of Intentions:
So I figured it out...
Here is the solution.
private func adjustFooterHeight() {
//Get content height & calculate new footer height.
let cells = self.tableView.visibleCells
var height: CGFloat = 0
for i in 0..<cells.count {
height += cells[i].frame.height
}
height = self.tableView.bounds.height - ceil(height)
//If the footer's new height is negative, we make it 0, since we don't need footer anymore.
height = height > 0 ? height : 0
//Create the footer
let footerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: height))
self.tableView.tableFooterView = footerView
}