Auto layout calculating same height for each collectionViewCell - ios

So I have a collection view that uses a custom layout that I found on Github called CHTCollectionViewWaterfallLayout I like it so far, however I am looking to make the custom collection view cell I made dynamic in height, and I am having trouble connecting Auto Layout to calculate this. I shared a simple sample project on Github that generates random string sizes and displays them, only problem is that Auto Layout generates the same cell height for each Collection View Cell. The project can be found here.
To give you a run down of my thought process, I calculate the cell height by using the method CHTCollectionViewDelegateWaterfallLayout sizeForItemAtIndexPath. Since I also use the delegates method columnCountforSection my thought is since I provide a finite number of columns based on the orientation, I take the collectionView frame width and I divide by the number of columns to get me my width for the cell.
func collectionView (collectionView: UICollectionView,layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
var cell = dict["heightCell"] as? CollectionViewCell
let randomString = RadomStrings[indexPath.item]
let float = CGFloat(columnCount)
let width = collectionView.bounds.size.width / float
if cell == nil {
cell = NSBundle.mainBundle().loadNibNamed("CollectionViewCell", owner: self, options: nil)[0] as? CollectionViewCell
dict["heightCell"] = cell
}
cell?.RandomStringLabel.text = randomString
cell!.frame = CGRectMake(0, 0, CGRectGetWidth(collectionView.bounds), CGRectGetHeight(cell!.frame))
cell!.setNeedsLayout()
cell!.layoutIfNeeded()
var size = cell?.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
size?.width = width
return CGSize(width: width, height: size!.height)
}
My constraints are pretty basic. I have a constraint on each axis. One on the top of the content view, leading, trailing and bottom. I also have a height on the label that is greater than or equal to 45.
Using Auto Layout to calculate TableViewCell heights is easy to me and I like it because I like the DRY principle behind this height calculation approach. So I would like to keep this height calculation process the same throughout my app. CollectionViews are a relatively new layout process for me, so I would love to learn what I am doing wrong here. Hopefully I am clear for everyone, thanks!

I figured it out! What I didn't do is put a width constraint on the custom cell I created! Duh!

Related

Append item horizontally into a tableView cell but make them not fill all the space

I have a specific design that I want to achieve, it doesn't seems to complex to me but I'm struggling a lot to make it happen.
First the design looks like this:
So, I have a table view of items which have a picture, then a title and then a collection of three items which will be ImageViews.
All of this inside a cell and inside a stackView.
I tried to make an horizontal stack view and did manage to append my items correctly but it went horribly wrong in terms of design as my imageviews were stretching all the way horizontally. I guess this is not possible to NOT stretch this items.
I also tried to add a collection view into the table view but figured out that was very complex for a thing this basic (as I guess it should be). And then, I'm here.
Here is my code and where I'm stuck at:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = debateList.dequeueReusableCell(withIdentifier: "itemsBox", for: indexPath) as! ItemBox
// Make a variable with 3 participants
let participants = tableView[indexPath.row].participants?.prefix(3)
// iterate over them and adding them to anything that can be doable
participants?.forEach { participant in
let imageView = UIImageView()
let imageUrl = URL(string: participant.imageURL)
imageView.kf.setImage(with: imageUrl)
// Here add item to something
}
return cell
}
I'm stuck and it's getting too long for something this little. Guess I'm a little bit upset with myself for not figuring out how to do it.
If you are having up to 5 participants, then I would suggest you to add by default 2 subviews in horizontal stack view:
empty view with width constraint >= 5 (call it spaceview)
fixed width for "60 % no" UILabel.
then programmatically insert image views with fixed width and aspect ratio 1:1 in horizontal stack view at index 0.
This way, if there will be only 1-2 participants, then the remaining space will be occupied by spaceview as its width will be greater than 5.
If the participants will be more than 5, then better option is to use collectionview with uilabel in horizontal stack view.
First if they are 3 static number of images then it's better to make them inside design and assign the image urls to their outlets respectively
Second for your current work you need to add a width constraint (regarding height they will fit all the stack height when it's a horizontal stack ) like
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.widthAnchor.constraint(equalToConstant: 50).isActive = true

CollectionView won't stop complaining about undefined behaviour

I have a set of 4 different elements that can be shown in a small horizontal collectionView. There will only be one row, and the collectionView and the elements will always be the same height as each other - however, it needs to be dynamic, in case people increase their text size (accessibility larger text).
To make this work, I have a "spacer" label next to the CollectionView, and tell the collectionView to be the same height as the label, and I do the same in all the cells. They all have the same font.
When I try to run it, the CollectionView prints out this:
The behavior of the UICollectionViewFlowLayout is not defined because:
the item height must be less than the height of the UICollectionView
minus the section insets top and bottom values,
minus the content insets top and bottom values.
Please check the values returned by the delegate.
The relevant UICollectionViewFlowLayout instance is <...>,
and it is attached to <..; frame = (0 438.5; 414 29.5);
clipsToBounds = YES; autoresize = RM+BM; gestureRecognizers = <..>;
layer = <CALayer:..>; contentOffset: {0, 0}; contentSize: {32, 29.5};
adjustedContentInset: {0, 0, 0, 0}> collection view layout: <...>.
Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes
to catch this in the debugger.
But everything I can find returns 29.5 or less.
collectionView.contentInsetAdjustmentBehavior = .never
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
ContentInset is .zero, sectionInset is 0 for top and bottom, lineSpacing and interItem-spacing is 0, scrollDirection = .horizontal..
I have not overridden any sizeForItem or anything like that, because it should happen automatically, and I shouldn't have to create custom height logic.
I have tried this in each cell:
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(UICollectionViewCell.layoutFittingCompressedSize)
print("Height: ", layoutAttributes.size)
return layoutAttributes
}
But it just prints out 29.5, which should be correct.
It does print it out after the warnings though, so something happens before this.
When I print out the height of the cell directly after its initialization like this:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let item = items[indexPath.row]
if let someItem = item as? SomeCellViewModel, let cell = self.dequeueReusableCell(withReuseIdentifier: "SomeIdentifier", for: indexPath) as? SomeCell{
cell.bind(with: someItem)
print("height: ", cell.bounds.size.height)
return cell
}else{ ...
it prints out "50.0". And as I understand, that's the default "Item Size", but I've changed all that! In Storyboard/IB/Xib, the "Collection View Flow Layout" was 50x50, but I have changed it to different things (0x0, 1x1, 20x20, tested a lot). Still it prints out 50.0. The xibs for the cells aren't even 50 in their file. If I try to use the systemLayoutSize-thing directly here to set its frame.size, it warns me about an autoresizing constraint that wants to be 0. I assume I shouldn't be doing such logic here anyway, so I nevermind this.
Since the warning says items "must be less than the height minus X minus Y" (not less than or equal to), I tried making the CollectionView a point taller than the spacer-logic, making the CollectionView 30.5 and the cells 29.5, but I still get the warning.
I have tried setting the symbolic breakpoint, but there is nothing of value there..
The thing is - it looks and behaves correct - on iOS 12. It does not work on iOS 11. There might be a separat constraint-issue within the elements that makes it wrong on iOS 11, I'm looking into it. Either way;
Why am I receiving this warning?
Edit: if I implement sizeForItemAt:IndexPath and statically tell it to be
width:100, height: 29.5, there is no warning at all. Everything is good. But I don't want to do that. I shouldn't need to calculate the individual height like this. And I don't really want to deal with prototype-cells either.
This should work.

How to set UITextView's height dynamically in UITableViewCell based on string size?

I understand that this question has been asked here, but it didn't solve my problem as the link in the accepted answer is down and the minimal example didn't help.
Here is a picture of my custom UITableViewCell & all its constraints:
Each post contains these UI elements. The only element that could make each cell's height different is messageView, because its height depends on the string being displayed. Question is, how do I dynamically set each cell's height? Here's what I have now (Does NOT work, messageView is not shown at all):
func cellForRowAt(indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(...) as! PostCell
let message = ...
cell.messageView.text = message
return cell
}
func heightForRowAt(indexPath: IndexPath) -> CGFloat {
var cellMinimumHeight: CGFloat = 120
if let cell = tableView.cellForRow(at: indexPath) as? PostCell {
let size = cell.messageView.sizeThatFits(cell.messageView.frame.size)
cellMinimumHeight += size.height
}
return cellMinimumHeight
}
in heightForRowAt function, the if is not being executed, therefore, all cells' heights are cellMinimumHeight = 120.
How could I make each cell's height = 120 + messageView's height?
---------------------------------EDIT 1---------------------------------
Made a mistake in the picture, messageView's height is not set
When using Auto-Layout for dynamically sizing cells, you don't really need to implement sizeThatFits(...). If the constraints are setup correctly, then you only need to disable the scrolling of the UITextView.
From code:
yourTextView.scrollEnabled = false
From IB:
Select your Text View and open Attributes inspector, then
In Attributes Inspector select your UITextView(messageView) and Uncheck "Scrolling Enabled".
And then change your UITextView(messageView)'s content compression resistence priority as follows:
Horizontal = 750
Vertical = 1000
I hope this will help you.
Just disable UITextview scroll...
But here is no use of UITextview, you can use label also.
In HeightForRow tableview delegate method remove that stuff and use
return UITableViewAutomaticDimension
You have used constrain to make cell hight dynamic
form apple documentation
To define the cell’s height, you need an unbroken chain of constraints
and views (with defined heights) to fill the area between the content
view’s top edge and its bottom edge.
So for your case you need
cell's height = 120 + messageView's height?
So start from Profile Image to measure unbroken chain of constraints from Top to Bottom
Profile Image top = 10 + ImageHeight = 60 ----> 70
MessageView top = 10 + set minimum height to message say 20 one line if every cell should have message even if one word and set this height Greater than or equal to 20 make sure that you set Scroll enable = false
so message Height minimum = 10 top + 20 + 10 bottom ---> 40
Menu Stack view Height ---> 30
So all Total = 70 + 40 + 30 = 140 this default hight no cell will be less than this
Also you must set the table view’s rowHeight property to UITableViewAutomaticDimension. You must also assign a value to the estimatedRowHeight property
tableView.estimatedRowHeight = 130.0
tableView.rowHeight = UITableViewAutomaticDimension
Here is apple documentation Here

Adjust UICollectionView Size Dynamically to Ensure No Inter Item Spacing

The goal is to have a UICollectionView with a specific number of columns per row, regardless of the size of the screen.
Currently I am using:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
//print(collectionView.frame.size.width)
let numberRows:CGFloat = 7
return CGSize(width: collectionView.frame.size.width/numberRows, height: collectionView.frame.size.width/numberRows)
}
To set the size of the cells so that they can fit inside the collection view without inter item spacing. However, the size of the collection view is set based on a percentage of the screen size. If the desired "numberRows" does not divide evenly, then there will be spacing between the cells.
How can I dynamically change the collection view size to ensure the cells will fit without spacing? How can this be accomplished in a more efficient way?
When you set the size of your collection view, you could round it down to a multiple of numberRows. This ensures it will divide evenly.
If you are using AutoLayout, you could set the width or height constraints of the collection view. Then call layoutSubviews: or setNeedsLayout
If you are not using AutoLayout, you can change the size of the collection view using setFrame or setBounds.
You can not break the mathematical rules here. If you get one point/pixel to much you can not break this pixel into two half-pixels.
What you can do is:
size the collection view accordingly so that the number of cells fit perfectly
accept that one of the cells will have one pixel more/less size then the others. (e.g. the last cell in the row)

How to adjust UICollectionView contentSize.height based on number of rows in swift

I'd really appreciate if someone can light up some ideas here. I've been trying to fix this for weeks now (not kidding).
I have a "to do list" using a collectionView where I hide the rows that were completed and move them to the end of the list. I then unhide the items if needed with a button. The collectionView looks exactly as a tableView with one item(cell) per row.
When the items are hidden the collectionView has a lot of empty scrolling space at the bottom instead of automatically deleting the space used by the hidden rows since they technically are still there.
I'm trying to cut that empty space so the collectionView height would be equal to the amount of cells/rows left visible.
override func viewDidAppear(animated: Bool) {
if isCellHidden { //checking if cells are hidden
var heightOfSection0 = 95 + (42 * self.collectionView.numberOfItemsInSection(0))
println(self.collectionView.contentSize.heigh) //returns 2350
self.collectionView.contentSize.height = CGFloat(heightOfSection0)
println(heightOfSection0) //returns 1019
println(self.collectionView.contentSize.heigh) //returns 1019.0 which is correct but as soon as I scroll down it resets to it's original size (2350) and let's me scroll through that empty space...
}}
If I try to read the collectionView height immediately after setting this, it displays the correct value but as soon as I try to scroll down it resets back to it's original height. I also tried disabling the auto layout and it doesn't make a difference
You should not manage contentSize directly - return appropriate number of items to be displayed from collectionView(_:numberOfItemsInSection:) instead (i.e. do not count your "hidden" cells).
You can use sizeForItemAtIndexPath: to change the size of collection view cell.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var numberOfCellInRow : Int = 3
var padding : Int = 5
var collectionCellWidth : CGFloat = (self.view.frame.size.width/CGFloat(numberOfCellInRow)) - CGFloat(padding)
return CGSize(width: collectionCellWidth , height: collectionCellWidth)
}
Changing UI stuff in viewDidAppear() in theory is a good place to do so. However, at this point, you can't be sure that auto layout has operated on the subview that you're manipulating.
You can check when it has and when it's safe to manipulate the frame, by subclassing it and overriding layoutSubviews()
I had a similar issue here: https://stackoverflow.com/a/30446125/4396258

Resources