Here is the UITableView that I want to make:
In order to achieve this, I divided contentView of cell into three parts.
1 - fromView: contains start time and class type. Height will be 30% of contentView
2 - infoView: contains clock image, class name and professor's name. Height will be 40% of contentView.
3 - toView: contains end time and location. Height will be space that left(30%).
Firstly, before going to details, I decided to show this containers only. For understanding a problem, I painted them(green, red, blue respectively).
After adding them into the contentView, here are the constraints I gave to those containers:
fromView.easy.layout(Top(), Left(), Right(), Height(*0.3).like(contentView))
infoView.easy.layout(Top().to(fromView), Left(), Right(), Height(*0.4).like(contentView))
toView.easy.layout(Top().to(infoView), Left(), Right(), Bottom())
Seems like everything is right. After launching, I thought that I will see them, but here is what happened:
I thought that cell does not know what size it needs to be, so I decided to implement following function:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 150
}
After this, UITableView showed containers pretty correctly than before. But, I plan to add some UILabels and other views into that containers(look at first picture). In other words, I don't know what size a cell needs to have. It needs to be kind of dynamic. Anyway, I tried to add those labels into the containers, maybe after that, a cell will figure out the the height. Here is how I did it:
func setupViews() {
fromView.addSubviews(fromTime, classType)
toView.addSubviews(toTime, room)
infoView.addSubviews(clockImage, teacherName, subjectName)
contentView.addSubviews(toView, fromView, infoView)
}
func setupConstraints() {
fromView.easy.layout(Top(), Left(), Right(), Height(*0.3).like(contentView))
infoView.easy.layout(Top().to(fromView), Left(), Right(), Height(*0.4).like(contentView))
toView.easy.layout(Top().to(infoView), Left(), Right(), Bottom())
fromTime.easy.layout(CenterY(), Left(8))
fromTime.setContentHuggingPriority(.required, for: .horizontal)
fromTime.setContentCompressionResistancePriority(.required, for: .horizontal)
classType.easy.layout(CenterY(), Left(8).to(fromTime))
clockImage.easy.layout(CenterY(), Left(16), Width(24), Height(24))
subjectName.easy.layout(CenterY(-8), Left().to(classType, .left), Right())
teacherName.easy.layout(Top(8).to(subjectName), Left().to(subjectName, .left), Right())
toTime.easy.layout(CenterY(), Left(8))
toTime.setContentHuggingPriority(.required, for: .horizontal)
toTime.setContentCompressionResistancePriority(.required, for: .horizontal)
room.easy.layout(CenterY(), Left(8).to(toTime))
}
But anyway, problem appeared again like this:
I think, the problem is that I give height to containers according to height of contentView, but contentView does not know its height. But I also don't know what height it needs to have, cause it needs to depend according to size of labels. So, how to solve this problem?
Don't set heights for the containers. You have to make sure the top and bottom of the cell are connected through constraints. My recommendation is to just get rid of the containers, add all the labels and images to the cell directly, and create constraints like this.
Then set tableView.rowHeight = UITableView.automaticDimension, or return UITableView.automaticDimension from tableView(_:heightForRowAt:)
Try by giving height constraint to your main view inside your cell.
Look like what you want is a dynamic height calculated based on the content's AutoLayout. Update the delegates to return the UITableViewAutomaticDimension for the heightForRowAt and some good estimation, e.g., 150 for the estimatedHeightForRowAt:
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 150
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Then you can just set the content's size using autolayout, and the table will calculate the height of the cells for you dynamically.
Estimated height is used for scrolling purposes - a good estimate will provide you a good scrolling experience (dynamic height is calculated only when the cell is being displayed, thus for the yet undisplayed cells the tableView will use estimation when scrolling).
Related
I'm looking for a way to resize a tableView cell with a more complex layout.
To simplify the description of the problem the tableview cell consists of three views. One view that defines the "background" of the cell and two additional views (each of them with a height 45% of the background cell.
One of these additional views is tagged to the top of the background view the other one to be bottom.
If the user taps on the tableview cell it should shrink in a way that only the top view is visible and after an additional user tap it should resize to its full size again.
The user tap is handled by the function
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
and the resizing is done by
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
Unfortunately, after shrinking the tableview cell, both addition views are displayed with half of their original hight.
var selectedCell = -1
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
tableView.deselectRow(at: indexPath, animated: true)
if (selectedCell != indexPath.row)
{
selectedCell = indexPath.row
}
else {
selectedCell = -1
}
tableView.beginUpdates()
tableView.endUpdates()
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if (indexPath.row == selectedCell)
{
return 65
}
else {
return UITableView.automaticDimension
}
}
I'm now looking for a way to change the code in a way, that after shrinking only the upper view is visible.
Example picture of the fully visible cell
In the example picture, after shrinking the tableview cell should only display the red view.
Thanks in advance
Patrick
The reason is that when the cell is shrunk, your views (since their sizes are defined relative to parent's size) also gets shrunk and are visible with half size. They are not really gone/hidden from the view. What you need is to hide/remove them from the view.
It looks from your description that you are using plain constraints to achieve this. This can be done by just using constraints but its a lot more work. So I will mention two ways to get this done:
Using just constraints
When the user taps your cell, you need to make the height of the bottom view 0. Also, if you do not want the middle 10% part to show when the cell shrinks, you would need to create an additional constraint from the bottom of the top view to the bottom of the contentView of the cell. Similarly, your bottom view will also have two height constraints, one for 45% and one for 0.
The idea is to have both these constraints at the same time but with different priorities. The initial constraint will have higher priority and the other one will have a lower priority. Although these constraints will be contradictory, iOS will take the higher priority one to render the view.
Next, you would need to toggle the active property of the higher priority constraint on user tap which will in turn let iOS use the lower priority constraint to render the view.
Use stackview:
iOS 9 introduced stack view which is a helper view. It basically, handles constraints for you, at least some part of it. When you hide one view from the stack view's children, stack view will automatically make the height of that view to be 0. read up more about vertical stack view and you will get the idea.
Hello I have a UITableView with x# of cells. the last cell I have two UILabels. When the second UILabel text is set I am trying to get the cell and the UILabel to resize to show the text.
Here is what I have:
The UILabel - LabelBio (orange) has:
Lines: 0
Baseline: Align Baselines
Line Break: Word Wrap
Autoshrink: Fixed Font Size
The constraints for ContentView, LabelSellerInfo and LabelBio are set as follows:
LabelSellerInfo
LabelBio
ContentView
With those settings here is what a get:
I have tried many variations but cannot seem to get the Bio label to grow and shrink, sometimes if I get the label to grow the cell is still too small.
Can anyone help me to understand what I am doing wrong and show me how to get the constraints correct to get this to work?
Thank you
Set tableView.rowHeight = UITableViewAutomaticDimension
Remove this function in your code func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
Set height constraint of LabelBio greaterOrEqualThan 0
here are the steps ...
hope you know about autylayout. use constraints (left to content view, right to content view, bottom to content view and verticle to your first label)
then in your viewdidLoad method add YourTableView.estimatedRowHeight = 200 -> give any height you want.
finally define below method :
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
you good to go.
I have a tableViewCell with a label inside that could be multiple lines tall. I've set the label's Lines property to 0. However, when I make the label's text have multiple lines the text gets cut off. Here's how I've set up my storyboard:
Does anybody know how I made the table's cells just tall enough to contain the labels within?
Setting Dynamic Cell height procedure
Pin the label from top and bottom. Please refer following Screen shot
Set numbers of line to 0 of the label as from property inspector of xcode, it can be done from code too please refer following screen shot
Implement delegates of table view mentioned below
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 50 // also UITableViewAutomaticDimension can be used
}
You are missing the bottom constraint from the label to the table view cell (as far as I can tell). In order to make autolayout know how large the height of the cell has to be, you need to supply those constraints.
In addition do not forget to provide the estimatedRowHeight to the table view. If that value is not given, automatic cell sizing will not work.
My iOS app has a tableview with one UITableViewCell with the following layout (1 stackview containing 2 labels and 1 button)
When the user taps the button the number of lines of the central label goes from 0 to 2 and will look like this:
Now there are two problems here:
1) Resizing the UIStackView
2) Resizing the cell
I have found a not-optimal solution for problem 1, which consists of adding an empty view in the stack. (invalidateIntrinsicContentSize was not working).
let emptyView = UIView()
emptyView.frame = CGRect(x: 0, y: 0, width: 0, height: 0)
stackView.addArrangedSubview(emptyView)
As you can see in the second screenshot the cell doesn't resize and I'm not sure if this is due to the stackview or the cell itself.
I would like to point the fact that I'm writing code inside the UITableViewCell subclass as the button event is handled inside it.
For the records The Tableview is using dynamic sizing:
// dynamic cell sizing
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat(cellEstimatedHeight)
}
UIStackView may or may not be your best bet... it's designed more for arranging views inside its own frame, rather than adjusting its own frame to the views.
For a layout as simple as you have there, it would probably be much better to just lay out the elements with normal constraints.
Take a look at this example: https://github.com/DonMag/DynamicCellHeight
Table B is one way of accomplishing your layout (Table A was for another layout I played around with).
I had to do a little trickery to get the main label to stay in place while the cell resized... could probably find a better way. (Re-Edit - yep, found a better way)
Let me know if it makes sense - no doubt you'll want to do some tweaking.
Senario A:
If I set the label content in cellForRowAtIndexPath, the cell correctly get resized.
Senario B:
If I change the text content in custom action in cell, the cell sized does not get changed.(I do call setNeedsLayout + layoutIfNeeded)
How to fix this?
EDIT:
1) I have set,
myTableView.estimatedRowHeight = 71.0
myTableView.rowHeight = UITableViewAutomaticDimension
2) I have correctly added auto layout constraints.
I was running into this issue, and my problem was that I was constraining the content to self (the UITableViewCell) and not to self.contentView (the contentView OF the cell). Hope this helps someone else who has built their cells all in code!
In my case, the cell's custom size was enabled:
After you change the text of the cell, just reload that particular cell or simply call mainTableView.reloadData().
To reload that cell-
//indexPath is indexPath of cell you just changed label of
mainTableView.reloadRows(at: indexPath, with: .automatic)
In my case, in the same cell I had an imageView in the top left corner with a "center vertically in container" constraint, and a "top space container" constraint.
Obviously to satisfy this two constraint the cell must have an height equal to:
(height of the imageView / 2) + (length of the top space container constraint).
This height is not enough to fit the label text, so my label had only 1 line visible.
After I have deleted the imageView top constraint all went to the right place, in my case i wanted the image to be centered, if the image had to stay in the top left corner I had to take off the "center vertically in container" constraint.
I hope this can help someone.
First of all, I don't specifically know what was your action on UITableViewCell. So, I assume I do that in UITableViewCell selection.
The below answer only work on iOS 9 and above
But, for some reason, it failed to do it in iOS 8 until it scroll. So, I will update the answer for iOS 8.
I have seen you have used UITableView's estimatedRowHeight and rowHeight at your project. So,
Please check the following
Make sure UITableView's estimatedRowHeight and rowHeight include inside viewDidLoad()
Make sure your UILabel lines set to 0
Make sure there is no constraints about height for your UILabel and let the constraints be like that :
If there are other component also included, make sure only bottom and top space constraints included or top space to container margin and bottom space to container margin.
Every time that you want to update the cell, you have to reload tableView no matter what your current situation will be.
So, don't say anything yet before you try this sample project, #Rikh answer still work. May be you are going in wrong direction. Here's the solution. Please do as I said steps by steps and let me know if that didn't work out. You might need to share your sample project which is causing.
Sample Demo - DynamicCellDemo
UPDATE for iOS 8 : update the following code for iOS 8 users
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if #available(iOS 9, *) {
// do nothing
} else {
tblDynamic.reloadData()
}
}
what you can do is set the AutoLayout constraints for the label in present in the cell from all the sides that is from Top, Bottom, Leading and Trailing. Then add the following UITableViewDelegate method to your class.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 500
}
This will do the job. As now content in table view cell automatically adjusts the height of the cell.
Try this if it works:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Add the following in your viewDidLoad()
-(void)viewDidLoad{
[super viewDidLoad];
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 140
}