How to maintain constraints even after deleting label in UITableViewCell - Swift 3 - ios

So currently, I have a tableviewcell that looks like this
What I want to happen, is that if an expense of the date already exists, the top label should disappear, the tableviewcell height should be reduced from 95 to 64 and everything should be centrally aligned. Sort of like this
I tried doing this many ways.
Use 2 different cells and switch, but that didn't work as only one expense was returned at a time and my tableviewcontroller didn't populate correctly.
Try using a stack view, but in that, I can't get the constraints to match as they are currently.
I have all the correct row height being returned in the heightForRowAtIndexPath method, but it centrally reduces the height and some of the data is cut.
How is it possible to achieve what I want to do (have the label not visible, the row height reduced and everything vertically center)?
Here is the code for switching of the cells.
func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat
{
if newMode==true {
return 95
}
else if newMode==false {
return 64
}
else {
return 0
}
}
This works, however it reduces the height from the top and the bottom and I only want the height from the top to be reduced.

You should not use the delegate method heightForRowAt instead you should use:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80
then give the (date of expense) label a hight constraint and connect an outlet to it.
in cellForRowAtIndex delegate method should have :
if expense != nil
{
lblExpenseConstraintHeight.constant = 0
}
else
{
lblExpenseConstraintHeight.constant = 34
}
cell.layoutIfNeeded()
Edit:
More info about about dynamic tableViewHeight:
Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
Another possible solution is remove the height constraint of the (date of expense) label and set it's text to empty string.

Related

Swift Dynamically growing a cell depending on the text length

I have a TableViewController which contains 2 sections:
Section 1 - is a cell which is loaded from a xib file. This cell just contains a TextView.
Section 2 - Contains multiple cells populated from an Array.
The section 1 only exists if the master (previous) UITableView cell you select contains a certain piece of data.
All of the above works as expected, below is the parent view. The list of items come from a database, some items have a description, and some do not. For example below this image, you'll see the view is 'Classic Starters' is selected. Then below that, you'll see the view if 'Stir Frys' is selected. Stri Frys contains a description:
Now, what I want is, the description cell which is shows on the Stir Frys page, to automatically grow depending on the length of the text inside it. So if a description is 10 lines long, it will grow to show all 10 lines.
Does this have to be done programmatically, or is their a feature in XCode I'm missing ?
You can use UITableViewAutomaticDimension.
1)
Set properties estimatedRowHeight and rowHeight of your tableview, in viewDidLoad for example. Like this :
self.tableView.estimatedRowHeight = 50
self.tableView.rowHeight = UITableViewAutomaticDimension
2) Return UITableViewAutomaticDimension in your heightForRowAt delegate method :
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
First you need to do is to set estimatedRowSize to a value that best estimates most common size, and rowSize to UITableViewAutomaticDimension:
self.tableView.estimatedRowHeight = 44
self.tableView.rowHeight = UITableViewAutomaticDimension
In your case, since except the first one all the other cells are supposed to be the same, you can use the height of the rest of the cells as the estimatedRowHeight.
You don't have to implement heightForRowAt at all.
The second step you need to do is to setup proper constraints on the cells. That means you have to constrain the contents of the cell to the left, right, top and bottom of the cell, so that when contents grow, the cell will need to grow, too. Common mistake is to forget to constrain bottom, so then the cell does not grow and the contents leak through the bottom of the cell.
Third, since your dynamic cell contains UITextView, you need to make sure that it will grow with its text. That is not automatic. To achieve that, based on this answer, this should suffice (in the cell):
textView.isScrollEnabled = false
If you are using storyboard, just uncheck scroll enabled.

Insert in a tableView set to Autoresize cause a scroll issue

I've an issue for few days and really I can't explain why it goes like that.
I'm doing a chat, set up with a tableView, printing message into cell. These cells are designed with prototypes, there are 3 different type (but anyway it doesn't matter). Text is typed, message is send, cell is inserted in table and then we scroll to the bottom of the tableView.
As you know, in a chat view the container of the message has to fit this text (which is a view), and then the cell has to fit to this container (Label in orange, container in purple).
This container has a variable height and grow along the text, changing cell height.
I've set many constraint for auto-layout display but I didn't have different cell height than the height I initially set for it in the project (no adaptative behaviour). Chat Message were cut.
So, I've tried to set each rowHeight by myself using the method heightForRowAtIndexPath, calculating constraint along text size but it create a real bad behaviour (changing cells when scrolling for example). The size was often wrong calculated/recalculated.
That's why I'm finally using estimatedRowHeight set to 44 combine with UITableViewAutomaticDimension. Here it does the trick ! Waouh ! But it's not as good as expected..
When the table view appears all is good. Containers fit to their label, rows' height fit to their container and it's beautiful. The problem appears during an insert.
After many tests, I notice that this bad behaviour only appears when multilines label remains in the table view.
During an insert, each cell seems to resize to 44 before adapt its height to content, and then create a gap compared to previous state which create a strange scroll of the table view :
If I change the estimatedRowHeight it made this worst.
I can show my code if you want, but I don't think it will be very useful here because I'm not using complicated function.. only insert, automatic height for cells and scroll down which are functions delegate for tableView.
Can you help me please ? Really I don't know how to change that.. every chat application does the trick but I can't found out the way.
Thank you for answer, excuse my english level I'm a poor french student..
If you need some code comment I'll give it.
I think you can cache height value for every cell in cellForRowAtIndex method and in HeightForRowAtIndex set the corresponding height for that cell.
You can just do this way :
var cellCached = Dictionary<Int,AnyObject>()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
table.rowHeight = yourCell.yourLabel.frame.origin.y + yourCell.yourLabel.frame.height + 10
cellCached[indexPath.row] = yourTable.rowHeight
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if(tableView == yourTable)
{
if(cellCached[indexPath.row] != nil)
{
return cellCached[indexPath.row] as! CGFloat
}
else
{
return 200
}
}
else
{
return UITableViewAutomaticDimension
}
}

Expanding tableviewcell based on UIlabel line count

I have a custom UITableViewCell with a UILabel that has multiple lines. I'm trying to implement a tap expand/contract feature so that when the label is only one line, no expansion happens when you tap the cell, but if the label is multiple lines, the cell is capable on expanding/contracting when tapped. I got the expansion and contracting working, but I can't figure out how to make it work based on the number of lines in the UILabel.
This is currently what I'm doing for the cell expansion, but its not based on the UILabel at all.
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if (indexPath.row == self.selectedRow?.row && self.expand == true)
{
self.previousRow = self.selectedRow?.row
return 200
}
else if(self.previousRow == self.selectedRow?.row && self.expand == false){
return 65
}
return 65
}
You can use table view dynamic cell sizing with autolayout in conjunction with the numberOfLines property of UILabel to expand and compress cell.
The heart of the solution is this:
tableView.beginUpdates()
label.numberOfLines = label.numberOfLines == 0 ? 1 : 0
tableView.endUpdates()
This is essentially triggering an animation on the table and toggling the number of lines from 0 (any number of lines) to 1 line. Autolayout is doing the rest.
I think this is better than updating height metrics manually. The full example project can be found on my github: https://github.com/rayfix/MultilineDemo
You probably need to measure the text yourself because the label won't have more lines until you expand.
Something like this (the 100000 just needs to be a big number)
CGSize textSize = [self.label.text sizeWithFont:self.label.font constrainedToSize:CGSizeMake(self.label.bounds.width, 100000) lineBreakMode: UILineBreakModeWordWrap];

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

UITableViewCell + Dynamic Height + Auto Layout

I came from this great answer: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
I've implemented the things described in that answer but I'm facing with a little different scenario. I haven't one UILabel but instead I have a dynamic list of UILabels.
I've created an image showing some different cases of what the table view should look:
At the current state of the repo the cell doesn't grow vertically to fit the cell's contentView.
UPDATE
REPO: https://github.com/socksz/DynamicHeightCellAutoLayout
If you try to get the project from the repo and run it, you can see exactly what is the problem I'm referring. I can't get what is missing for let it works.
The problem here is with the third party component you are using, FXLabel, not with any of the code around table views or Auto Layout in them. In order to support Auto Layout, custom subclasses of UIView must implement the -[intrinsicContentSize] method appropriately, and then call -[invalidateIntrinsicContentSize] when something changes it.
In this case, FXLabel appears to be relying on its superclass implementation (UILabel) for the above methods, and since UILabel was not designed to handle variable line spacing in the way that FXLabel implements it, it doesn't know the correct intrinsicContentSize to return, and therefore the Auto Layout calculations are wrong (in this case, since the intrinsic content size is too small). Check out the "Enabling Custom Views for Auto Layout" section of this excellent obcj.io article for more details.
Now the good news is that as of iOS 6, you should be able to accomplish this using an attributed string in a standard UILabel. Check out the Stack Overflow answer here.
If for some reason you really like FXLabel, perhaps you could open an issue on the GitHub project (or try and fix it yourself and submit a pull request).
To set automatic dimension for row height & estimated row height, ensure following steps to make, auto dimension effective for cell/row height layout.
Assign and implement dataSource and delegate
Assign UITableViewAutomaticDimension to rowHeight & estimatedRowHeight
Implement delegate/dataSource methods (i.e. heightForRowAt and return a value UITableViewAutomaticDimension to it)
-
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Don't forget to set dataSource and delegate for table
table.dataSource = self
table.delegate = self
// Set automatic dimensions for row height
// Swift 4.2 onwards
table.rowHeight = UITableView.automaticDimension
table.estimatedRowHeight = UITableView.automaticDimension
// Swift 4.1 and below
table.rowHeight = UITableViewAutomaticDimension
table.estimatedRowHeight = UITableViewAutomaticDimension
}
// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// Swift 4.2 onwards
return UITableView.automaticDimension
// Swift 4.1 and below
return UITableViewAutomaticDimension
}
For label instance in UITableviewCell
Set number of lines = 0 (& line break mode = truncate tail)
Set all constraints (top, bottom, right left) with respect to its superview/ cell container.
Optional: Set minimum height for label, if you want minimum vertical area covered by label, even if there is no data.

Resources