I am having some hard time as usual getting the constraints correct. This is how my tableview works:
This is the default display. The height of the cell is 57.
When you click on a cell, i change the selected cells height to 90 so it displays this information:
My problem is to set the constraints so it looks exactly as it does in both images. What is the best solution to this?
Would it be easier if i made two views and placed them inside of my uitableview so the constraints will be set for the specific view instead of the cell's frame which is being changed on user click etc. I uploaded my project if anybody wants a better overview of what is going on:
https://ufile.io/u2y5p
You can also put your information inside a UIStackView and make them invisible until you click on the cell, use the UITableViewAutomaticDimension to make the cell's height dynamic, so when you will make those information visible, reloading the UITableView will make your cell's height higher
Suppose you are displaying the data on TableView from an array of models. Keep a property in your model class named as "isExpanded" and set its value by default false.
Now, use below code:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let dataModel = self.arrayOfModels[indexPath.row]
return dataModel.isExpanded ? 100 : 50
}
On select a row:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let dataModel = self.arrayOfModels[indexPath.row]
dataModel.isExpanded = true
self.tableView.reloadRows(at: [indexPath], with: .automatic)
}
Let me know if you have any doubt, or need more clarification.
Related
I have a problem with reusing a cell.
In my cell there is a view that changes the constraints depending on its state. By default, cell height = 0, when the user clicks on the button, it changes its constraints top, leading, trailing and bottom. The problem is that if I expand, press expand view in one cell, scroll down (or up), then another cell will also be expanded.
I change the variable responsible for uncovering the cell in the prepareForReuse method, and it doesn't help :(
My solution used to be like this: I redid the constraints in the configure method (it is called in the cellForRow method in VC), and it worked, BUT there were lags when scrolling, apparently, because I change the constraints every time a cell is configured and reused
Are there ways to avoid lag when scrolling? Is there another way to do this?
I use SnapKit for make layout.
I would try avoiding making the cell have a height of zero. This may mess with table view and create an overhead in multiple situations. Since cells are dequeued to reduce resource consumptions you should not produce zero-sized cells as theoretically you can see infinite number of such cells in screen. So basically you can easily have a situation where hundreds of cells are being processed by your table view which user is not even seeing.
An alternative to zero-height cell is to insert/delete cells from table view. There are even animations for that already in place. To achieve this you need to correct your numberOfRows method and your cellForRowAt where both of them need to ignore "hidden" cells. Beside that you basically just need something like
func hideCell(indexPath: IndexPath) {
tableView.beginUpdates()
self.items[indexPath].isHidden = true
tableView.deleteRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
}
func showCell(indexPath: IndexPath) {
tableView.beginUpdates()
self.items[indexPath].isHidden = false
tableView.insertRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
}
This should fix most (all) of your issues. But if you still want to stay with having small/zero sized cells there are other ways to fix your issue.
When you change your constraints you should also call (maybe you already do that but did not post it)
cell.remakeParentCommentConstraints()
tableView.beginUpdates()
tableView.endUpdates()
and make sure that when cellForRow is being called you also call cell.remakeParentCommentConstraints() because as cell is dequeued you can not know what state the previous cell was in.
Even when doing all of this correctly you may experience jumping. This is now because estimated height logic is not very smart by default and you need to improve it. A quick fix is to cache your cell heights:
private var cellHeightCache: [IndexPath: CGFloat] = [:]
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeightCache[indexPath] ?? 44.0
}
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeightCache[indexPath] = cell.bounds.height
}
The part with 44.0 should be whatever you have currently set as estimated row height.
This trick may get a bit more complicated if you insert/delete rows (as in first solution). As you also need to insert/remove heights from cache. It is doable, just not as easy.
I solved the problem by dividing the states into different cells
So I am creating a facebook feed of sorts and I am fetching text, url for images/videos and username etc. The cell I have created has an image view, text view and label. I am sizing the cell according to the text view content size.
Now lets say the user has only posted text and there no image with it. I want the cell to be sized so that there is no image view in the cell. Only the text view and thats it.
Can this be done using just one cell/model/adapter or do I need custom cells and show them according to the model.
A little light on how this could be done would be appreciated.
You just need one cell and there are different ways of achieving this. A simple oen is using a UIStackView inside the cell.
When one element of the UIStackView is hidden, it adapts it size. If you place constraints from to the stack view to the cell, the cell with be smaller/larger depending if you show the image. You just have to work with constraints.
Also, using UIStackView or not, remember to specify on your tableView that the size of the cell will be dynamic by doing:
self.tableView.rowHeight = UITableViewAutomaticDimension
Create a outlet of image height constraint, and then check if your URL is blank or invalid then make height constraint constant 0 otherwise give it appropriate height.
And make sure you have implemented both the methods :
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 100; // Put estimated height
}
And another one is,
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
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.
I am working on a ViewController with a TableView populated with dynamic cells from a prototype nib. I have run into a dead-end trying to recreate certain a cell-expanding animation.
My goal:
When a cell is selected, the selected cell should "expand" (grow in height to twice it's starting size) while at the same time creating space between the cell directly above and below the selected cell.
I've found an example of EXACTLY the asethetic I am looking for in the app "Things". Below are two screenshots showing the table before and and after a cell is selected:
Screenshot of table BEFORE expansion
Screenshot of table AFTER expansion
The best way I can describe the desired animation is the UITableView version of "the parting of the Red Sea". When a cell is selected, the surrounding cells give way lending the selected cell more room.. and the user's focus.
What I've tried:
I found Simon Lee's method answering a similar question and implemented it into my project. And although it animates the row-height change perfectly, it only pushes the adjacent cells on one side of the selected cell. (ie: if the cell at index 4 is selected, all the cells from index 5+ move down but those from index 0-3 stay static. Thereby not achieving the look I'm seeking.)
Using that method, the relevant sections of my code looked something like:
var cellHeight: CGFloat = 72
var selectedCell: IndexPath?
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath == selectedCell {
return cellHeight * 2
} else {
return cellHeight
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedCell = indexPath
tableView.beginUpdates()
tableView.endUpdates()
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
selectedCell = nil
tableView.beginUpdates()
tableView.endUpdates()
}
Because that didn't result in my desired animation, I tried insertRows:at:with: to insert 2 empty cells (1 above and 1 below the selected row) -- then deleteRows:at:with: to remove them upon deselecting the cell. This ultimately made for a better looking animation, and looked closer to the "Things" example I'm shooting for. However this made the table overly complicated because by adding and removing cells each time a row is selected, it would change the index of the other cells making it frustrating to predict which cells would have what index at any given time.
A possible solution idea?
After working on this for a couple of days the only other way I could think to accomplish what I want is to somehow scroll the table slightly at the same time that the selected cell's height it changed.. so that as the cell expands (moving the following cells downward) it would make the previous cells appear to move upward. I'm hesitant to try this because it feels like a hack, there should be a better way to accomplish this.
ANY help would be thoroughly appreciated! I've been pulling my hair out at an alarming rate. Thank you to anyone who can share their knowledge.
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.