I'm developing an iOS Chat App and I have a problem on the chat view. I'm using UITableViewController for the chat view. Sometimes my table jumps when new row is inserted as you can see in video: https://youtu.be/8IgEUJ5uYAc .
This is how I'm inserting and scrolling to the bottom of the table:
self.conversation.append(message)
self.tableView.beginUpdates()
self.tableView.insertRows(at: [IndexPath(row: self.conversation.count - 1, section: 0)], with: UITableViewRowAnimation.none)
self.tableView.endUpdates()
DispatchQueue.main.async {
self.tableView.scrollToRow(at: IndexPath(row: self.conversation.count - 1, section: 0), at: UITableViewScrollPosition.bottom, animated: false)
self.footerView?.isHidden = true
self.theMessage.text = nil
self.switchBottomActions(showSend: false)
}
Each message object has a property called estimatedMessageHeight. I'm saving there the message cell size, so tableView's heightForRowAt code is:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
guard let cellHeight = self.conversation[indexPath.row].estimatedHeight else {
return 0
}
return cellHeight
}
Any solution?
I was also facing same issue and finally I ended up with following solution. It gives me same animation feel like we have in WhatsApp app.
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: [], animations: {
let reloadIndexPath = IndexPath(item: self.arrTableVwData.count - 1, section: 0)
self.tableVw.beginUpdates()
self.tableVw.insertRows(at:[reloadIndexPath], with: UITableViewRowAnimation.fade)
self.tableVw.endUpdates()
self.tableVw.scrollToRow(at: reloadIndexPath, at: .bottom, animated: false)
}, completion: nil)
NOTE: please do not pass any animation types in options:[] as we already passing them in insertRows and scrollToRow methods.
Make this function:
func scrollToBottom(){
DispatchQueue.main.async {
let indexPath = IndexPath(row: self.chatListDB.count-1, section: 0)
self.tblView.scrollToRow(at: indexPath, at: .bottom, animated: false)
}
}
and call it after inserting the value.
You should uncheck Bounce Vertically in the Attribute Inspector for your UITableView.
Related
I am trying to write a function that scrolls to the bottom cell of a given section in my tableView, I'm calling this function after my tableView.reloadData() function but it is not scrolling, any ideas why?
func scrollToBottom(section: Int){
DispatchQueue.main.async {
let indexPath = IndexPath(row: self.session[section].count - 1, section: section)
self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
}
You don't need put code in DispatchQueue.main because it already run on main thread.
Try this
func scrollToBottom(section: Int){
let indexPath = IndexPath(row: self.session[section].count - 1, section: section)
self.tableView.scrollToRow(at: indexPath, at: .top, animated: true)
}
Trying to call a TableView row to be selected programatically.
All my delegates are set properly, and the UITableView is linked in IB. All other UITableView methods work properly, and are located in either the main ViewController or its extension. When the following is called I get Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value.
func selectTableViewRowProgrammatically () {
let indexPath = IndexPath(row: someVariable, section: 0)
tableView.selectRow(at: indexPath, animated: false, scrollPosition: UITableView.ScrollPosition.none)
tableView.delegate?.tableView!(self.tableView, didSelectRowAt: indexPath)
tableView.reloadData()
}
Error shows on self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: UITableView.ScrollPosition.none)
Thanks!
can you try Following code
let indexPath = IndexPath(row: someVariable, section: 0)
UIView.animate(withDuration: 0.1, animations: {
tableView.scrollToRow(at: indexPath, at: .top, animated: false)
}) { (true) in
tableView.delegate?.tableView!(self.tableView, didSelectRowAt: indexPath)
}
I'm developing an app which has a main tableview in home screen. When I tap on a certain type of cell of the tableview, I do a little animation and then I delete the cell. I tried the behaviour on all simulator. Only on 4,7" and 5,5" I get a crash when I try to delete a certain row, systematically. I can't understand the error, since Xcode only says: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
This is my code:
tableView.deselectRow(at: indexPath, animated: false)
let incomingReminderTableViewCell = cell as! IncomingReminderTableViewCell
self.tableView.isUserInteractionEnabled = false
incomingReminderTableViewCell.completeReminderAt(indexPath: indexPath) { (indexPath) in
let selectedReminder = self.incomingReminders.reversed()[indexPath.row-1]
if CoreDataManager.sharedInstance.delete(selectedReminder) {
UserNotificationManager.decreaseBadge()
UserNotificationManager.deleteNotificationsWith(identifiers: [selectedReminder.idNotification])
self.incomingReminders = self.incomingReminders.reversed()
self.incomingReminders.remove(at: indexPath.row-1)
self.incomingReminders = self.incomingReminders.reversed()
if self.incomingReminders.isEmpty {
UIView.animate(withDuration: 0.2, animations: {
incomingReminderTableViewCell.alpha = 0.0
}, completion: { (disappeared) in
self.tableView.reloadRows(at: [indexPath], with: .fade )
self.tableView.isUserInteractionEnabled = true
})
} else {
UIView.animate(withDuration: 0.2, animations: {
incomingReminderTableViewCell.alpha = 0.0
}, completion: { (disappeared) in
self.tableView.isUserInteractionEnabled = true
self.tableView.deleteRows(at: [indexPath], with: .right) // HERE THE CRASH
self.incomingReminders = CoreDataManager.sharedInstance.getIncomingReminders()
if self.incomingReminders.count == 5 {
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: { (timer) in
self.tableView.insertRows(at: [IndexPath(row: self.incomingReminders.count, section: 0)], with: .fade)
timer.invalidate()
})
}
})
}
} else {
self.tableView.isUserInteractionEnabled = true
}
}
When app crash I have indexPath = (row: 3, section: 0), that is right. DataSource has been correctly updated, before deleteRows I have two element in the array and the data in the tableview start at indexPath = (row: 1, section: 0). Can't find a solution.
Not so convinced, but I've solved in this way:
tableView.performBatchUpdates({
tableView.deleteRows(at: [indexPath], with: .right)
}, completion: { (deleted) in
tableView.reloadData()
self.tableView.isUserInteractionEnabled = true
self.incomingReminders = CoreDataManager.sharedInstance.getIncomingReminders()
if self.incomingReminders.count == 5 {
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: { (timer) in
self.tableView.beginUpdates()
self.tableView.insertRows(at: [IndexPath(row: self.incomingReminders.count, section: 0)], with: .fade)
self.tableView.endUpdates()
timer.invalidate()
})
}
})
The problem is after deleted a certain number of rows in tableview, it remained dirty. In fact, deleting a bunch of rows, scrolling, and then continuing to delete, crash didn't happen. I needed to reload data. I put
tableView.deleteRows(at: [indexPath], with: .right)
as a batch update. And in completion block I called the reload, with other things. All seems ok for now. Hope this can be useful for someone.
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 ...)
I am trying to scroll horizontally to a collectionView when I call a function and am receiving the error 'none' is unavailable: use [] to construct an empty option set - not sure what is wrong here...Thanks in advance!
func handleSearch() {
scrollToMenuIndex(menuIndex: 2)
}
func scrollToMenuIndex(menuIndex: Int) {
let indexPath = NSIndexPath(item: menuIndex, section: 0)
collectionView?.scrollToItem(at: indexPath as IndexPath, at: .none, animated: true)
}
You have to provide a position where the item should be scrolled to. See the documentation for UICollectionViewScrollPosition for possible values. In the meantime you could use this:
collectionView?.scrollToItem(at: indexPath as IndexPath, at: .left, animated: true)