I am currently working on a app that shows nearby annotations to user by TableView. Cells are ordered by distance to user's location. I want to animate the cells when they change order due to distance. How can I do that ?
Here's my view
I had tried this code but it didn't work.
tableView.beginUpdates()
tableView.moveRow(at: IndexPath(row: fromIndex, section: 0), to: IndexPath(row: toIndex, section: 0))
tableView.endUpdates()
Edit : I tried like this one it didn't work like the way I want, but it was really close. The problem is, all the cells are animating when a single cell changes it's order
didUpdateLocations :
sortedAnnotationArrayWithModel = annotationArrayWithModel.sorted { $0.distance < $1.distance }
tableView.reloadData()
tableView.beginUpdates()
for i in 0..<sortedAnnotationArrayWithModel.count {
let oldIndex = annotationArrayWithModel.firstIndex(of: sortedAnnotationArrayWithModel[i])
let newIndex = i
if oldIndex != newIndex {
tableView.moveRow(at: IndexPath(row: oldIndex!, section: 0), to: IndexPath(row: newIndex, section: 0))
}
}
tableView.endUpdates()
Try wrapping your changes in a call to performBatchUpdates(_:completion:)
You pass that function a closure that does the reordering, and it animates the changes.
Related
I was wondering if it is posible to reload a tableView without reloading the first cell/row in a simple way? If so, how would you do it?
let itemArray:[Int] = []
for i in 1..<itemArray.count {
let indexPath = IndexPath(item: i, section: 0)
tableView.reloadRows(at: [indexPath], with: .fade)
}
If your table is not grouped currently ,
The best way to do this is add your 1st cell to one section And all other cells to another section. Then you can easily reload all cell excluding 1st cell using
//section 0 - your 1st cell
//section 1 - other cells
tableView.reloadSections([1], with: .none)
Just use the reloadRows method to reload your tableView.
var dataSource: [SomeData] = []
func reloadAllButFirstRow() {
guard dataSource.count > 0 else { return }
tableView.reloadRows(
at: (1...dataSource.count).map { IndexPath(row: $0, section: 0) },
with: .fade
)
}
I am creating a chat interface.
User's message is put in a new UITable view cell.
And when update the table view, I use the following code.
extension UITableView {
func scrollToBottom() {
let rows = self.numberOfRows(inSection: 0)
if rows > 0 {
let indexPath = IndexPath(row: rows - 1, section: 0)
self.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
}
}
Actually this works a little better, but there is something strange.
When I turn off the app and turn it on again, or after exiting the screen and entering again, the following issues arise.
The issue is that when I add a new cell, it goes up to the first cell in the table view and back down to the last cell.
See the issue(https://vimeo.com/266821436)
As the number of cells increases, the scrolling becomes too fast and too messy.
I just want to keep updating the last cell that is newly registered.
What should I do?
Please use DispatchQueue to Scroll because of the method you are fire is executed with tableView load data so we need to give time to scroll.
extension UITableView {
func scrollToBottom() {
let rows = self.numberOfRows(inSection: 0)
if rows > 0 {
DispatchQueue.main.async {
let indexPath = IndexPath(row: rows - 1, section: 0)
self.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
}
}
}
or
func scrollToBottom(){
DispatchQueue.main.async {
let indexPath = IndexPath(row: self.array.count-1, section: 0)
self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
}
self.scrollToRow(at: indexPath.last ...)
When i insert row at index path in uitableview, then my tableview scroll to top? Why?
let indexPathForCell = NSIndexPath(forRow: 5, inSection: 1)
tableView.beginUpdates()
tableView.insertRowsAtIndexPaths([indexPathForCell], withRowAnimation: .Automatic)
tableView.endUpdates()
All code that is invoked during the addition of the cell
func buttonDidPressed(button: CheckMarkView) {
let indexPathForCell = NSIndexPath(forRow: 5, inSection: 1)
buttonPressedTag = button.tag
for checkMark in buttons {
if checkMark.tag == buttonPressedTag {
if buttonPressedTag == 4 {
checkMark.show()
checkMark.userInteractionEnabled = false
cellWithCategories["Recomendation"]?.append("slideCell")
tableView.beginUpdates()
tableView.insertRowsAtIndexPaths([indexPathForCell], withRowAnimation: .None)
tableView.endUpdates()
}
checkMark.show()
} else {
if (tableView.cellForRowAtIndexPath(indexPathForCell) != nil) {
cellWithCategories["Recomendation"]?.removeLast()
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths([indexPathForCell], withRowAnimation: .None)
tableView.endUpdates()
}
checkMark.hide()
checkMark.userInteractionEnabled = true
}
}
}
code for number of rows :
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionKey = keysForSectionTableView[section]
let numberOfRows = cellWithCategories[sectionKey]
return (numberOfRows?.count)!
}
I don't see any code that will make your table view scroll to top.
But you can try change animation to none. If doesn't work then there is must be some other code, thats causing this issue.
let indexPathForCell = NSIndexPath(forRow: 5, inSection: 1)
tableView.beginUpdates()
tableView.insertRowsAtIndexPaths([indexPathForCell], withRowAnimation: .None)
tableView.endUpdates()
I am attempting to make the last row in a UITableView visible, after it has been added. Right now, when I add a row and call reloadData, the table goes to the top.
I figure if I get the indexPath for the last row, that I can select that row and it should appear in the list. I am unsure of how to get that value, or even if I am approaching this correctly.
How do I get an indexPath for a specific row?
Please note that, you don't need to call the reloadData to make the last row visible. You can make use of scrollToRowAtIndexPath method.
You can use the below code to achieve your goal.
// First figure out how many sections there are
let lastSectionIndex = self.tblTableView!.numberOfSections() - 1
// Then grab the number of rows in the last section
let lastRowIndex = self.tblTableView!.numberOfRowsInSection(lastSectionIndex) - 1
// Now just construct the index path
let pathToLastRow = NSIndexPath(forRow: lastRowIndex, inSection: lastSectionIndex)
// Make the last row visible
self.tblTableView?.scrollToRowAtIndexPath(pathToLastRow, atScrollPosition: UITableViewScrollPosition.None, animated: true)
Swift 4.0:
tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.none, animated: true)
You can use scrollToRowAtIndexPath with extension:
In Swift 3:
extension UITableView {
func scrollToLastCell(animated : Bool) {
let lastSectionIndex = self.numberOfSections - 1 // last section
let lastRowIndex = self.numberOfRows(inSection: lastSectionIndex) - 1 // last row
self.scrollToRow(at: IndexPath(row: lastRowIndex, section: lastSectionIndex), at: .Bottom, animated: animated)
}
}
Shamsudheen TK's answer will crash
if there is no rows/sections in tableview.
The following solution to avoid crash at run time
extension UITableView {
func scrollToBottom() {
let lastSectionIndex = self.numberOfSections - 1
if lastSectionIndex < 0 { //if invalid section
return
}
let lastRowIndex = self.numberOfRows(inSection: lastSectionIndex) - 1
if lastRowIndex < 0 { //if invalid row
return
}
let pathToLastRow = IndexPath(row: lastRowIndex, section: lastSectionIndex)
self.scrollToRow(at: pathToLastRow, at: .bottom, animated: true)
}
}
Note: If you are trying to scroll to bottom in block/clousure then you need to call this on main thread.
DispatchQueue.main.async {
self.tableView.scrollToBottom()
}
Hope this will helps other
As suggested by others get indexPath for perticular sections like section 0.
After that call...add this methos in cellFOrROwAtIndex
[tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; ..to scroll to specific indexPath in TableView.
Note:-But it still need scrolling of tableview in Downward direction.
You shouldn't be using -reloadData for this use case. What you're looking for is -insertRowsAtIndexPaths:withRowAnimation:.
Feel free to ask if you want some usage examples or a more detailed explanation as to why using -reloadData send you to the top of the UITableView.
Not mandatorily required to get the indexpath of last row.
You can set the CGPoint of UITableview to show a last row you added.
I always use this code in my chat application to show a last added message.
//Declaration
#IBOutlet weak var tableview: UITableView!
//Add this executable code after you add this message.
var tblframe: CGRect = tableview.frame
tblframe.size.height = self.view.frame.origin.y
tableview.frame = tblframe
var bottomoffset: CGPoint = CGPointMake(0, tableview.contentSize.height - tableview.bounds.size.height)
if bottomoffset.y > 0 {
tableview.contentOffset = bottomoffset;
}
I hope it will work for you.
Thanks.
Swift 5.0 +
extension UITableView {
func isLastVisibleCell(at indexPath: IndexPath) -> Bool {
guard let lastIndexPath = indexPathsForVisibleRows?.last else {
return false
}
return lastIndexPath == indexPath
}
}
I have different sections and rows. Each row has a variable height. I am expanding and collapsing the table by inserting and deleting row. But when i expand/collapse, the other cells overlaps with each other causing a glitch.
func expandItemAtIndex(index:Int) {
var indexPaths = NSMutableArray()
var currentSubItems = contentsArray.objectAtIndex(index) as NSArray
var insertPos = 0
for(var i = 0; i < currentSubItems.count; i++) {
indexPaths.addObject(NSIndexPath(forRow: insertPos++, inSection: index))
}
self.tableview.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
}
func collapseItemAtIndex(index: Int) {
var indexPaths = NSMutableArray()
for (var i = 0; i < contentsArray.objectAtIndex(index).count; i++) {
indexPaths.addObject(NSIndexPath(forRow: i, inSection: index))
}
self.tableview.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
}
func expandOrCollapseCells(object:AnyObject) {
self.tableview.beginUpdates()
if (expandedIndex[index] == index) {
self.collapseItemAtIndex(expandedIndex[index])
expandedIndex[index] = -1
} else {
expandedIndex[index] = index
self.expandItemAtIndex(expandedIndex[index])
}
self.tableview.endUpdates()
if (self.tableview.numberOfRowsInSection(index) > 0) {
self.tableview.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: index), atScrollPosition: UITableViewScrollPosition.Top, animated: true)
}
}
I am calling expandOrCollapseCells when the header is tapped.
You can find similar question here
Video: https://www.youtube.com/watch?v=0YTHu9u88_Y
I'm going to assume you aren't using Swift 3 due to the syntax differences.
The one thing that really sticks out is this line:
self.tableview.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
Have you tried using UITableViewRowAnimation.Top instead?
Because it seems to me like the "animation" visible in the video is being forced by beginUpdates() and endUpdates(), since the animation value is set to None in the actual method.
I'd also not recommend using another cell to expand/collapse content from any given cell if you don't specifically need the expanded content to be a table cell for some reason. If that isn't the case, then I'd suggest laying out the expanded content in the parent level cell structure and simply hiding/unhiding it as needed.