I currently have a table view controller which consist of not only the cells but also a UIView. Now, within that UIView there's a label which might have more than 1 line of text with a See More button. When I pressed that button, the button itself will disappear and the text.numberOfLines is set to 0 so the View should expand to show all text. Which doesn't seems to work in my case, The button does disappear though and the text just continues to the edge of the screen, truncated instead of extending down.
But when this whole UIView is outside the table view controller the functions above work just as expected, but not after I've moved them to within the table View right above the prototype cells. Any Ideas?
When you are setting up the tableviewcell, you have to specify a fixed height for the row with the delegate function
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) {
if expandedArray[indexPath.row] {
return 60
} else {
return UITableViewAutomaticDimension
}
}
Then keep an array of bools, to specify if the cell is expanded or not. Primarily set the bools to false indicating "not expanded".
Then when the use presses the "see more" button, make the bool in the array with the right index true, indicating that the cell is expanded. and call the below code updating the cell.
tableView.beginUpdates()
tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
tableView.endUpdates()
That should do the trick.
P.S: Don't forget to properly add constraints inside tableview cell.
Use UITableViewAutomaticDimension and reload your cell on click of see more button.
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
I struck in tableview. I need tableview of collapse and Expand looks like Below,
]2
I need to add 2 buttons for last row of every section.
Could you guide me how to design the below design in ios swift?
Add your buttons in custom view and feed it in UITableViewDataSource method below:
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView?
Best way is take Two Button in one cell, and return that cell when indexPath.row = lastRow
And add tap gesture on header view for collapse view
You can create a cell with two buttons. Next you should add it to your data source and configure it properly. Then add a delegate to your custom UITableViewCell and handle taps.
I am having a little trouble with buttons on a tableview.
I have a tableViewCell that I customised with 3 buttons. I set the buttons to hidden in interface builder and when the table loads the buttons are hidden as expected.
I then set the hidden property of the tableview to false when didSelectRow is called and hidden.true when didDeselectRow is called. This works fine as well. The problem is the buttons that are set to visible in the didSelectRow are also visible every seven cells down. they keep repeating themselves.
Below is the code that shows the buttons
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! ContactsViewCell
print("Table selected")
cell.insertEmailButton.hidden = false
cell.insertPhoneButton.hidden = false
cell.insertAllButton.hidden = false
cell.contactTextLabel.alpha = 0.2
cell.contactDetailTextLabel.alpha = 0.2
}
And this hides them when the tableViewCell is deselected
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! ContactsViewCell
cell.insertEmailButton.hidden = true
cell.insertPhoneButton.hidden = true
cell.insertAllButton.hidden = true
cell.contactTextLabel.alpha = 1.0
cell.contactDetailTextLabel.alpha = 1.0
}
I did some research and I learnt it might be the row with the buttons.hidden set to false are being reused by the tableview. But I understand from documentation that the cell being reused is from cellForRowAtIndexPath and not the cell at didSelectRow which is where I am setting the button.hidden to false.
I also tried using the cell.isSelected property in an if else statement in the cellForRowAtIndexPath to hide and show the buttons but this does not show the buttons at all.
Thanks in advance for your help
The tableview reuses the view of the cell when the table is scrolled, to save memory. So, for example, when you set the button to visible (inside didSelectRow) and then scroll down the table, the tableview will take the cells that go out of the visible screen at the top and will reuse them at the bottom, to save the overhead of creating new cells, improving performance.
That is why, your previous properties on the cells are repeating.
To get the desired hidden button on scrolled cells, I recommend setting button.hidden to true/false in
tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
This will set the button to hidden whenever a new row is scrolled into the visible view area.
Hope this helps.
I solved the recurring buttons by hiding them when I check if the cell is deselected in cellForRowAtIndexPath. This also means any cell I select will lose its selected status and buttons will disappear when it leaves the view.
I can live with that.
if cell.selected == false{
cell.emailButton.hidden = true
cell.phoneButton.hidden = true
cell.allButton.hidden = true
}
UITableView reuses its cell to improve performance. So, you can not do the way you are trying. What we have to do is, like other tableview cell info e.g. title, description, thumb image etc we also need to save the state for buttons in the array. When you want to hide a button for the cell take object at index from the array and change the button state for the button and reload that table view cell. Still if you face problem or feel difficult to understand, please feel free to ask.
I am trying to make a '''Static Table''' with some cells that are expandable and collapsing . I override the ''': WithAnimation:)''' which should provide me some control on the animation transition. Unfortunately, the cells returned are blank, I don't mean empty, but the cells have disappeared and their location is left blanc in the table. When I try using the '''reloadSections( )''' rather, the whole section is also returned blank. After some recherche , I start to suspect that '''reloadRowAtIndexPath(: WithAnimation:)''' and '''reloadSections( )''' works with table associated with a data source.
Am I right? If yes, how can I control animation of the cell I want to hide/unhide so that the transition is smooth?
Thanks (edited)
To get a smooth animation, simply reload the tableView with
tableView.beginUpdates()
tableView.endUpdates()
this will call the heightForRowAtIndexPath automatically and render a smooth transition.
To expand on irkinosor's answer. In this example I have 1 section with 3 static rows. When tableView is initially loaded, my UISwitch is off and I only want the first row (row 0) be visible (hide rows 1,2). When the UISwitch is on, I want to show (rows 1,2). Also, I want smooth animation having the rows accordion to hide or show.
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row > 0 && toggleSwitch.isOn == false {
return 0.0 // collapsed
}
// expanded with row height of parent
return super.tableView(tableView, heightForRowAt: indexPath)
}
In my switch action I initiate the tableView refresh
#IBAction func reminderSwitch(_ sender: UISwitch) {
// to initiate smooth animation
tableView.beginUpdates()
tableView.endUpdates()
}
You can obviously add more logic to the heightForRowAt function if you have multiple sections and more interesting row requirements.
I'm using a "Static Cells" table view created in my storyboard file. However I'd like to update the text on one of these cells when a setting is changed.
I'm pushing a view controller which updates the setting. I'm trying to use a callback to change the cell's text when it's popped off the stack, but by that point the cell has apparently been recycled or reused, so the old object is off screen and no longer used in the table view.
Is there a way I can update this text, and make it permanent (so that when the cell is scrolled off screen and comes back, the new value still appears)?
Assuming your table view hierarchy is along the lines of:
Table View (static cells)
- Table View Section
- Table View Cell
- UILabel (with the text property you want to modify)
Declare an IBOutlet UILabel in your code, and wire it up in the storyboard's UILabel in the table view hierarchy above.
In your callback method, set your UILabel's text property as you see fit.
You can store text that you want to change as a property and use it in:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = super.tableView(tableView, cellForRowAtIndexPath: indexPath)
switch (indexPath.section, indexPath.row) {
case (0, 3):
cell.textLabel?.text = yourProperty
default: break
}
return cell
}