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 ...)
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)
}
I have a horizontal collectionview that adds a new cell every time I tap the button. I am attempting to scroll the collectionview to the next cell once the cells are no longer visible each time I tap the button.
The code I have now is not working properly
Code:
#IBAction func random(_ sender: Any) {
resultsCollection.reloadData()
let collectionBounds = resultsCollection.bounds
let contentOffset = CGFloat(floor(resultsCollection.contentOffset.x - collectionBounds.size.width))
self.moveToFrame(contentOffset: contentOffset)
}
func moveToFrame(contentOffset : CGFloat) {
let frame: CGRect = CGRect(x : contentOffset ,y : resultsCollection.contentOffset.y ,width : resultsCollection.frame.width,height : resultsCollection.frame.height)
resultsCollection.scrollRectToVisible(frame, animated: true)
}
How can I fix this so that it scrolls correctly when I tap the button?
After reload your data, call this lines.
let lastItem = resultsCollection(resultsCollection, numberOfItemsInSection: 0) - 1
let indexPath = IndexPath(row: lastItem, section: 0)
resultsCollection.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
Use the following code to achieve your needs. For animation, you just need to pass value true/false in animated of scrollToRow function.
Hope this will help you!
To scroll top without animation
func scrollToTopWithoutAnimation() {
DispatchQueue.main.async {
if self.dataArray.count > 0 {
let indexPath = IndexPath(row: 0, section: 0)
collectionView.scrollToItem(at: indexPath, at: .top, animated: false)
}
}
}
To scroll top with animation
func scrollToTopWithAnimation() {
DispatchQueue.main.async {
if self.dataArray.count > 0 {
let indexPath = IndexPath(row: 0, section: 0)
collectionView.scrollToItem(at: indexPath, at: .top, animated: true)
}
}
}
Set IndexPath row as per your needs
What i am trying to do is scrolling my CollectionView to the very bottom as soon as my content has been loaded.
Here is my code;
func bindSocket(){
APISocket.shared.socket.on("send conversation") { (data, ack) in
let dataFromString = String(describing: data[0]).data(using: .utf8)
do {
let modeledMessage = try JSONDecoder().decode([Message].self, from: dataFromString!)
self.messages = modeledMessage
DispatchQueue.main.async {
self.collectionView.reloadData()
let indexPath = IndexPath(item: self.messages.count - 1, section: 0)
self.collectionView.scrollToItem(at: indexPath, at: .bottom, animated: false)
}
} catch {
//show status bar notification when socket error occurs
}
}
}
but it is totally not working.
By the way i'm using InputAccessoryView for a sticky data input bar like in iMessage and using collectionView.keyboardDismissMode = .interactive
thanks,
What I am think about that you maybe call the func before your collectionView getting visible cells or call reload collection view and then scroll items immediately, at this point scroll to indexPath will not make sense, since the collection view's content size is not yet updated.
scrollToItem(at:at:animated:)
Scrolls the collection view contents until the specified item is visible.
try this:
self.collectionView.reloadData()
let indexPath = IndexPath(item: self.messages.count - 1, section: 0)
let itemAttributes = self.collectionView.layoutAttributesForItem(at: indexPath)
self.collectionView.scrollRectToVisible(itemAttributes!.frame, animated: true)
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)
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
}
}