I made it work to to collapse the section with cells but at the end i reload all the tableview and i don't want that. What i want is to reload only the section when it expands or not (inser/delete).
Here is the method which i use to insert or delete the rows of my section:
func settingsViewTapped(view: SettingsCellView, section: Int) {
var indexPaths = [IndexPath]()
for row in self.settingsTrainings[section].workouts.indices {
let indexPath = IndexPath(row: row, section: section)
indexPaths.append(indexPath)
}
let isExpanded = self.settingsTrainings[section].isExpanded
self.settingsTrainings[section].isExpanded = !isExpanded
if isExpanded {
tableView.deleteRows(at: indexPaths, with: .none)
tableView.reloadData()
} else {
tableView.insertRows(at: indexPaths, with: .none)
tableView.reloadData()
}
}
You should wrap deleteRows and insertRows with beginUpdates and endUpdates
tableView.beginUpdates()
tableView.insertRows(at: indexPaths, with: UITableViewRowAnimation.top)
tableView.endUpdates()
Related
I've got some problems fixing voiceover after deleting a row. the structure is like this:
I've got a tableview with 2 sections.
The first section got a header of height = 0 and only one row of variable height.
The second section got a header with fixed height with a button inside; rows in this sections can be 'n'.
When the user tap the button inside the header the cell in the first section is deleted or re-inserted according to the previous state.
In the normal 'state' with the cell expanded the voiceover works perfectly. When the user taps the button and delete the row in the first section the voiceover breaks. If I browse from top to bottom it's all ok. Instead when you scroll upwards the vo reads the cells visible on the screen but reads the header of the first section before the cells below it.
Insert and deleting is pretty simple:
let indexPath = IndexPath(row: 0, section: 0)
if isExpanded {
if tableView.contentOffset.y <= 0 {
tableView.insertRows(at: [indexPath], with: .automatic)
} else {
tableView.reloadData()
tableView.scrollToRow(at: indexPath, at: .top, animated: true)
}
} else {
tableView.deleteRows(at: [indexPath], with: .automatic)
}
cells in each sections have: isAccessibilityElement = false
the accessibility element is the card inside the cell like this:
cardView.isAccessibilityElement = true
cardView.accessibilityTraits = .button
I would really appreciate your help, I have tried different solutions but none of them work. It's pretty much a headache!
Let me know if you need more info to solve this.
Thank you.
Solved this by keeping always a row in the first section.
tableView.performBatchUpdates({
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.insertRows(at: [indexPath], with: .fade)
}, completion: nil)
But when the variable isExpanded is false the height of the rows in first section is set to 0 instead of automatic.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 {
return isExpanded ? UITableView.automaticDimension : 0
}
return UITableView.automaticDimension
}
I have an expandable UITableView. When sections tapped, they expand or collapse with animation (Scroll). My problem is that there is a weird animation when expanding or collapsing headers. UITableView scrolls to top and then goes to the tapped cell. In addition, when there is no expanded cell, sometimes, It doesn't scroll to top and there is a big space between top header and top view of UITableView.
My problem is that I need to scroll to expanded section and also get rid of scroll to top bug.
I tried below solution but didn't work for me:
prevent table view to scrolling top after insertRows
It also looks like same problem with below question, but can't figure out how to implement it.
Why does my UITableView "jump" when inserting or removing a row?
How I toggle selection:
func toggleSection(header: DistrictTableViewHeader, section: Int) {
print("Trying to expand and close section...")
// Close the section first by deleting the rows
var indexPaths = [IndexPath]()
for row in self.cities[section].districts.indices {
print(0, row)
let indexPath = IndexPath(row: row, section: section)
indexPaths.append(indexPath)
}
let isExpanded = self.cities[section].isExpanded
if(isExpanded){
AnalyticsManager.instance.logPageEvent(screenName: analyticsName!, category: "Button", action: Actions.interaction, label: "\(self.cities[section].name) Collapse Click")
}else{
AnalyticsManager.instance.logPageEvent(screenName: analyticsName!, category: "Button", action: Actions.interaction, label: "\(self.cities[section].name) Expand Click")
}
self.cities[section].isExpanded = !isExpanded
// This call opens CATransaction context
CATransaction.begin()
// This call begins tableView updates (not really needed if you only make one insertion call, or one deletion call, but in this example we do both)
tableView.beginUpdates()
if isExpanded {
tableView.deleteRows(at: indexPaths, with: .automatic)
} else {
tableView.insertRows(at: indexPaths, with: .automatic)
}
// completionBlock will be called after rows insertion/deletion animation is done
CATransaction.setCompletionBlock({
// This call will scroll tableView to the top of the 'section' ('section' should have value of the folded/unfolded section's index)
if !isExpanded{
self.tableView.scrollToRow(at: IndexPath(row: NSNotFound, section: section) /* you can pass NSNotFound to scroll to the top of the section even if that section has 0 rows */, at: .top, animated: true)
}
})
if self.scrollToTop(){
self.tableView.setContentOffset(.zero, animated: true)
}
// End table view updates
tableView.endUpdates()
// Close CATransaction context
CATransaction.commit()
}
private func scrollToTop() -> Bool{
for sec in self.cities{
if(sec.isExpanded){
return false
}
}
return true
}
I'm giving height of cell inside;
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
How I declare headers;
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = DistrictTableViewHeader()
header.isColapsed = !self.cities[section].isExpanded
header.customInit(title: self.cities[section].name, section: section, delegate: self)
return header
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60
}
EDIT: Solution in this question (Setting estimated height to 0) looks like working when inserting row. However, I still have bug when deleting rows. Bottom header goes to center of tableview and then goes to bottom after collapse header.
iOS 11 Floating TableView Header
You can try using below code. Just get the last content offset of your tableview. Then do the update and reassign the content offset.
let lastOffset = tableView.contentOffset
self.tableView.beginUpdates()
self.tableView.layer.removeAllAnimations()
self.tableView.endUpdates()
tableView.contentOffset = lastOffset
Instead of tableView.beginUpdates() and tableView.endUpdates(), In my Code i'm using tableView.reloadData() for expanding and contracting the particular section, You can call reloadData when you need to provide expansion of section.This results that you don't have the problem of animation scroll to the top. And works fine in my project where I have to show number of rows in particular section on a click of button which includes in that section.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
}
// Ignore the If Else statements it's just when do you need expansion of section.
else {
if showMore == true {
return self.userPoints.rewardsData[section - 1].count - 1
}
else {
return 0
}
}
}
Also Don't Forget to increase or decrease the number of rows to that particular section accordingly.Previous line is important to avoid any crash.
Simple Solution swift3 and Above
use below extension as
eg: tableViewOutlet.tableViewScrollToBottom(animated: true)
extension UITableView {
func tableViewScrollToBottom(animated: Bool) {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
let numberOfSections = self.numberOfSections
let numberOfRows = self.numberOfRows(inSection: numberOfSections-1)
if numberOfRows > 0 {
let indexPath = IndexPath(row: 0, section: 0)
self.scrollToRow(at: indexPath, at: UITableView.ScrollPosition.top, animated: animated)
}
}
}
}
I also facing same issue but after read some tutorials and research & Analysis I got the this issue occurred due to height of cell when you expand the section at that tableview count height of cell from 0 to 120(as per your cell height).
In my case I solved that issue using estimated height of cell.
I hope that will help you,
Thanks
I have a TableViewController with rows that hold tasks. If a task has an attribute task.done = 1 I want to move it at the bottom from the table.
I can't provide any code, because I have no doubt how to do this.
My idea was in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) using following code:
let element = tasks.remove(at: indexPath.row)
tasks.insert(element, at: tasks.count)
The issue is, that this needs to be done after the table is loaded, because if the first row is done = 1 for example, it will be moved to to bottom and will be processed at the end again.
You can programmatically remove a row from UITableView and insert a row programmatically. Before doing the operations on the UITableView, make sure to remove/add a specific item to the data source array. Otherwise, it'll just crash.
If you want to simply move the rows, you can use the code below. You need to do it in the place where the array which holds the data source is updated.
tableView.moveRow(at: oldIndexPath, to: newIndexPath)
If you want to do delete and insert new objects into the array, you may try the method as shown below.
let element = tasks.remove(at: indexPath.row)
tableView.deleteRows(at: indexPath, with: .automatic)
tasks.insert(element, at: tasks.count)
tableView.insertRows(at: [IndexPath(row: tasks.count, section: 0)], with: .automatic)
Wrap your code in a beginUpdates-endUpdates block.Use func moveRow(at indexPath: IndexPath, to newIndexPath: IndexPath)
For example: Not tested
self.tableView.beginUpdates()
let element = tasks.remove(at: indexPath.row)
let movingRowIndex = indexPath.row
tasks.insert(element, at: tasks.count)
let indexPath = NSIndexPath(forRow: movingRowIndex, inSection: 0)
var lastIndexPath = NSIndexPath(forRow:tasks.count, inSection: 0)
self.tableView.moveRow(at indexPath: IndexPath, to newIndexPath: lastIndexPath)
self.tableView.endUpdates()
Is there any way that I could reload only tableViewCell only but don't make it reload tableView section title View ? when I switched UITableViewCell I want to update data reload change
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch self[selectedIndex] {
case .tracks:
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! TracksCell
return cell
case .playlists:
let cell = tableView.dequeueReusableCell(withIdentifier: cellPlayListId, for: indexPath) as! PlaylistCell
return cell
}
What I want to do is just reload UITableViewCell only I don't want to reload this code below
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {}
if I use tableView.reload() it will effected my style in viewForHeaderInSection Because I have added UIViewScroll
thank you for your help!
If you want to reload only particular tableView cell then use this:
tableView.reloadRows(at: [IndexPath(row: rowNumber, section: 0)], with: .automatic)
It reload only cell not the section.
Reload the Particular UITableViewCell
tableviewCart.reloadRows(at: [indexPath!], with: .none)
Look for the table view delegate methods for reloading views. These are:
open func reloadSections(_ sections: IndexSet, with animation: UITableView.RowAnimation)
open func reloadRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation)
open func reloadData()
Here in your case, reloadRows will be the best suitable option, as:
tableView.reloadRows(at: <[IndexPath]>, with: .automatic)
Where, <[IndexPath]> is the array of index path of the cells. For example:
let cell0FromSection0 = IndexPath(row: 0, section: 0)
let cell2FromSection0 = IndexPath(row: 2, section: 0)
let cell1FromSection1 = IndexPath(row: 1, section: 1)
tableView.reloadRows(at: [cell0FromSection0, cell2FromSection0, cell1FromSection1], with: .automatic)
You can reload particular cell in UITableView with below code.
let cellNmber = IndexPath(row: rowNumber, section: sectionNummber)
tableView.reloadRows(at: [cellNmber], with: .automatic)
or
tableView.reloadRows(at: [cellNmber], with: .none)
If you want to reload cell with default animation then go with .automatic.
If you want to reload cell without animation then go with .none.
You can also reload multiple cell in a tableView by providing IndexPath array.
e.g.
tableView.reloadRows(at: [cellNmber1,cellNmber3,cellNmber5], with: .automatic)
I need the cells of a UITableView to be expanded, but I see a flicker when doing this.
I have this implementation:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Switch row state
expandedFlags[indexPath.row] = !expandedFlags[indexPath.row]
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: { () -> Void in
tableView.beginUpdates()
tableView.reloadRows(at: [IndexPath(row: indexPath.row, section: 0)], with: UITableViewRowAnimation.automatic)
tableView.endUpdates()
}, completion: nil)
}
Is there any way to avoid the flicking when reloading the cells? I've read several posts but didn't work for my scenario.
tableView.beginUpdates()
....
tableView.endUpdates()
already has its own animation UIView animation what probably makes the flicker
Hi After spending so many hours on this, I finally found the solution to stop this flicker issue in table view while you are expanding or collapsing your tableview cell.
In First step, you have to get your current offset of tableview and then stop the uiview animation and tell your tableview that you are going to update some rows in the tableview. Then pass your current index [IndexPath(row: 0, section: 3)] like this is mine. Your tableview works like charm.
let currentOffset = self.tableView.contentOffset
UIView.setAnimationsEnabled(false)
self.tableView.beginUpdates()
self.tableView.reloadRows(at: [IndexPath(row: 0, section: 3)], with: .none)
tableView.endUpdates()
UIView.setAnimationsEnabled(true)
self.tableView.setContentOffset(currentOffset, animated: false)