Add a padding function in a tableView (Swift) - ios

I would like to adjust this function (that was created originally for a collectionView)
let place = places[indexPath.row]
let annotationPadding = CGFloat(5)
let font = UIFont.systemFont(ofSize: 15)
let commentHeight = place.heightForComment(font, width: width)
let height = annotationPadding + commentHeight + annotationPadding
return height
So I can add it in func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { } in my tableView, how can I do? (My problem is that CGFloat do not accept width)

Assuming heightForComment for the collection view takes the width of the collection view cell, you can pass in the width of the table view, since table view cells are as wide as their containing table view:
let commentHeight = place.heightForComment(font, width: tableView.bounds.size.width)
If there is additional leading or trailing margins for the comment in the table view, you can subtract that from the table view width:
let commentHeight = place.heightForComment(font, width: tableView.bounds.size.width - (20 * 2)) // 20 points margin on left and right

Related

Struggling to make collection view height fit content

I have a bit of a special scenario and no matter what StackOverflow answer I find, it doesn't seem to work for me. I have a collection view with a dynamic number of cells (anywhere between 2-3 cells). However, the height of this collection view will always be the same, no matter how many cells there are.
Here is the logic I have for setting the cell heights:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// The cell's width and height, to be calculated
var cellWidth: CGFloat = 0.0, cellHeight: CGFloat = 0.0
// The number of cells to show, 2-3
let count = numberOfCellsToShow
// The width of the collection view
let collectionViewWidth = collectionView.bounds.size.width
// Calculate the maximum height for collection view (16:9 ratio)
let collectionViewMaxHeight = collectionViewWidth / (16 / 9)
// Get the width and height of each cell, with 4dp of spacing between cells
if (count == 2) {
cellWidth = (collectionViewWidth - CGFloat(4)) / 2
cellHeight = collectionViewMaxHeight
} else if (count == 3) {
cellWidth = (collectionViewWidth - (CGFloat(4) * 2)) / 3
cellHeight = collectionViewMaxHeight
}
return CGSize(width: cellWidth, height: cellHeight)
}
Now, I want to make sure that the height of the collection view fits the content, but this is where I'm struggling to make it happen.
My collection view is inside a stack view, with all constraints set to the edges of the stack view.
What do I need to do to set the height of my collection view to fit the contents?

Calculating height of UICollectionViewCell with text only

trying to calculate height of a cell with specified width and cannot make it right. Here is a snippet. There are two columns specified by the custom layout which knows the column width.
let cell = TextNoteCell2.loadFromNib()
var frame = cell.frame
frame.size.width = columnWidth // 187.5
frame.size.height = 0 // it does not work either without this line.
cell.frame = frame
cell.update(text: note.text)
cell.contentView.layoutIfNeeded()
let size = cell.contentView.systemLayoutSizeFitting(CGSize(width: columnWidth, height: 0)) // 251.5 x 52.5
print(cell) // 187.5 x 0
return size.height
Both size and cell.frame are incorrect.
Cell has a text label inside with 16px margins on each label edge.
Thank you in advance.
To calculate the size for a UILabel to fully display the given text, i would add a helper as below,
extension UILabel {
public static func estimatedSize(_ text: String, targetSize: CGSize = .zero) -> CGSize {
let label = UILabel(frame: .zero)
label.numberOfLines = 0
label.text = text
return label.sizeThatFits(targetSize)
}
}
Now that you know how much size is required for your text, you can calculate the cell size by adding the margins you specified in the cell i.e 16.0 on each side so, the calculation should be as below,
let intrinsicMargin: CGFloat = 16.0 + 16.0
let targetWidth: CGFloat = 187.0 - intrinsicMargin
let labelSize = UILabel.estimatedSize(note.text, targetSize: CGSize(width: targetWidth, height: 0))
let cellSize = CGSize(width: labelSize.width + intrinsicMargin, height: labelSize.height + intrinsicMargin)
Hope you will get the required results. One more improvement would be to calculate the width based on the screen size and number of columns instead of hard coded 187.0
That cell you are loading from a nib has no view to be placed in, so it has an incorrect frame.
You need to either manually add it to a view, then measure it, or you'll need to dequeu it from the collectionView so it's already within a container view
For Swift 4.2 updated answer is to handle height and width of uicollectionview Cell on the basis of uilabel text
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
let size = (self.FILTERTitles[indexPath.row] as NSString).size(withAttributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14.0)])
return CGSize(width: size.width + 38.0, height: size.height + 25.0)
}

UICollectionView Custom Layout Middle column starting higher

I have a UICollectionView scrolling vertically right now. I would like to have it look like this =, where the middle row starts a little higher than the other two to create a cool and interesting effect.
Any ideas on how to achieve this?
The Collection view layout can be achieved by creating a custom layout class.
Theory:
Basically, collection view works directly with the custom layout object to manage the overall layout process, asking for the required layout information.
During the layout process, the collection view calls specific methods of the layout object. These methods provide a chance to calculate the position of items and to provide the collection view with the primary information it needs.
Following methods are always called in order during the layout process:
prepare(): Perform the up-front calculations needed to provide layout information
collectionViewContentSize: Return the overall size of the entire content area based on your initial calculations
layoutAttributesForElements(in:): Return the attributes for cells and views that are in the specified rectangle
Practical:
Prerequisite: Assuming we have a Collection view in place and configured with the
datasource and delegates, let's create a UICollectionViewLayout
subclass. I named it HiveLayout. I also assigned it to the
collectionView in the storyboard. We also need some variable that will be useful in the process
// Properties for configuring the layout: the number of columns and the cell padding.
fileprivate var numberOfColumns = 3
fileprivate var cellPadding: CGFloat = 10
// Cache the calculated attributes. When you call prepare(), you’ll calculate the attributes for all items and add them to the cache. You can be efficient and query the cache instead of recalculating them every time.
fileprivate var cache = [UICollectionViewLayoutAttributes]()
// Properties to store the content size.
fileprivate var contentHeight: CGFloat = 0
fileprivate var contentWidth: CGFloat {
guard let collectionView = collectionView else {
return 0
}
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
}
prepare(): This is where we will actually calculate the attributes of the cells
override func prepare() {
// If cache is empty and the collection view exists – calculate the layout attributes
guard cache.isEmpty == true, let collectionView = collectionView else {
return
}
// xOffset: array with the x-coordinate for every column based on the column widths
// yOffset: array with the y-position for every column, Using odd-even logic to push the even cell upwards and odd cells down.
let columnWidth = contentWidth / CGFloat(numberOfColumns)
var xOffset = [CGFloat]()
for column in 0 ..< numberOfColumns {
xOffset.append(CGFloat(column) * columnWidth)
}
var column = 0
var yOffset = [CGFloat]()
for i in 0..<numberOfColumns {
yOffset.append((i % 2 == 0) ? (columnWidth / 2) : 0)
}
for item in 0 ..< collectionView.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)
// Calculate insetFrame that can be set to the attribute
let cellHeight = columnWidth - (cellPadding * 2)
let height = cellPadding * 2 + cellHeight
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
// Create an instance of UICollectionViewLayoutAttribute, sets its frame using insetFrame and appends the attributes to cache.
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
cache.append(attributes)
// Update the contentHeight to account for the frame of the newly calculated item. It then advances the yOffset for the current column based on the frame
contentHeight = max(contentHeight, frame.maxY)
yOffset[column] = yOffset[column] + height
column = column < (numberOfColumns - 1) ? (column + 1) : 0
}
}
collectionViewContentSize:
// Using contentWidth and contentHeight, calculate collectionViewContentSize.
override var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
layoutAttributesForElements(in:):
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var visibleLayoutAttributes = [UICollectionViewLayoutAttributes]()
for attributes in cache {
if attributes.frame.intersects(rect) {
visibleLayoutAttributes.append(attributes)
}
}
return visibleLayoutAttributes
}
layoutAttributesForItem(at:): To return attributes for particular cell
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cache[indexPath.item]
}
Please check out the gist for the Layout Class.
Here it is in action!!

coding for custom uicollectionview layout

Where/How would I code for this cell layout behavior.
I need each cell to overlap the previous.
Cell 2 centerX is Cell 1 right edge and so forth...
What method would I override in my custom UICollectionViewLayout?
When creating a custom collectionView layout, override layoutAttributesForItem to configure cells layout behavior...
var preferredSize: CGSize? = CGSize(width: 100, height: 100)
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.size = preferredSize!
//Modifying the centerX property adjust the overlapping effect
let centerX = (preferredSize?.width)! / 2.0 + CGFloat(indexPath.item) * ((preferredSize?.width)! * 0.5 )
let centerY = collectionView!.bounds.height / 2.0
attributes.center = CGPoint(x: centerX, y: centerY)
attributes.zIndex = -(indexPath.item)
return attributes
}

Set TableView height by the number or rows

I have got TableView in the MainstoryBoard and the number of rows is random every time.
I want the height of the whole TableView to be flexible - by that I mean, for example: if I got 4 rows in the TableView and each TableView row height is 22 so the TableView height will be 88.
Another example:
number of rows: 2
row height = 22
TableView will be 44.
How can I make it?
You can change the UITableView height as per the contentSize as below:
Swift 2.2
tableView.frame = CGRectMake(tableView.frame.origin.x, tableView.frame.origin.y, tableView.frame.size.width, tableView.contentSize.height)
Swift 3 or 4+
tableView.frame = CGRect(x: tableView.frame.origin.x, y: tableView.frame.origin.y, width: tableView.frame.size.width, height: tableView.contentSize.height)
and make sure you write the above line in viewDidAppear method
You need to write the above line in viewDidLayoutSubviews also.
Swift 2.2
func viewDidLayoutSubviews(){
tableView.frame = CGRectMake(tableView.frame.origin.x, tableView.frame.origin.y, tableView.frame.size.width, tableView.contentSize.height)
tableView.reloadData()
}
Swift 3 or 4+
func viewDidLayoutSubviews(){
tableView.frame = CGRect(x: tableView.frame.origin.x, y: tableView.frame.origin.y, width: tableView.frame.size.width, height: tableView.contentSize.height)
tableView.reloadData()
}
Simply take outlet for tableview height constraint and update it in willDisplay cell: UITableViewCell method of UITableView
#IBOutlet weak var tblHConstraint: NSLayoutConstraint!
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
self.tblHConstraint.constant = self.tblView.contentSize.height
}
nothing worked here is the solution i ended up using which gives accurate height.
extension UITableView {
var contentSizeHeight: CGFloat {
var height = CGFloat(0)
for section in 0..<numberOfSections {
height = height + rectForHeader(inSection: section).height
let rows = numberOfRows(inSection: section)
for row in 0..<rows {
height = height + rectForRow(at: IndexPath(row: row, section: section)).height
}
}
return height
}
}
Usage:
tableView.contentSizeHeight
will give you the actual calculated height of your table view content.
Take outlet of the Height Constraint of the parent view and assign it the height of table view's content + constant(extra height of other contents in the view)
heightConstraintView.constant = tableView.contentSize.height + constantValue //use the value of constant as required.
and write this code in the cellForRowAt method.
To use with with autolayout:
Somewhere in viewDidLoad()
tableView.anchor(top: view.topAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor)
and then in your viewDidLayoutSubViews()
tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true
DispatchQueue.main.async {
var frame = tableView.frame
frame.size.height = tableView.contentSize.height
tableView.frame = frame
}
OR
DispatchQueue.main.async {
self.TblViewHeightConstraint.constant = CGFloat((self.array.count) * 30)//Here 30 is my cell height
self.TblView.reloadData()
}
Please find the below code.
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UIScreen.main.bounds.height
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let tableViewHeight = self.tableView.frame.height
let contentHeight = self.tableView.contentSize.height
let centeringInset = (tableViewHeight - contentHeight) / 2.0
let topInset = max(centeringInset, 0.0)
self.tableView.contentInset = UIEdgeInsets(top: topInset, left: 0.0, bottom: 0.0, right: 0.0)
}
Use this for Swift 3
override func viewDidLayoutSubviews(){
tableView.frame = CGRect(x: tableView.frame.origin.x, y: tableView.frame.origin.y, width: tableView.frame.size.width, height: tableView.contentSize.height)
tableView.reloadData()
}
Note: when your are increasing the tableview height it will goes out side the view. and you will get a problem with scrolling.
Ex: take a view inside that keep tableview and give constraints to left,right,bottom,top to Zero(0).
now reload the tableview assume 2 rows each row height is 20 now total rows height is 40. its fine and gave those height to table view, Table view will show only 40 height (you can assign tableview height by taking constraints (or) table view content size both are give same height according to rows height).
But if you reload 50 rows you will get scrolling problem because those rows height given to table view height, here table view height expands more than your screen height.
To solve this problem you should use scroll view outside the tableview.
Take outlet of tableview height and set it with your tableview row height in cellForRowAt like this,
tableviewHeight.constant = (tableView.contentSize.height * no_of_rows) + bottom_space

Resources