I have a UITableView which contains a message thread conversation. I'm trying to allow the user to scroll up to view previous messages, and seamlessly load previous messages. I'm able to append the previous messages just fine, but the UITableView jumps when I append the messages. How can I append messages without the table view jumping. Here is the code I have when I append the messages:
// Create the index paths for the updated messages
let indexPaths = (0..<newMessages.count).map { NSIndexPath(forItem: $0, inSection: 0) }
// Append new items
self.messageThreadTableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
The newMessages array contains the messages to be appended to the UITableView, which were appended correctly to the UITableView's data source array. Is there another method to append to a table view which won't make the scrolling jump?
Related
So I have a view controller that has a table view. This view controller has a button that when clicked opens another view controller. There is a button on this view controller that will change the data set for the table view and dismiss the view controller.
Problem is when changing the data set and dismissing the view controller it calls cellForRowAt. But because the number of items has the potential to decrease I get an Index out of range error.
After setting some break points I realize this is because after updating and dismissing the view controller cellForRowAt gets called but numberOfRowsInSection doesn't. So the number of rows has updated but that isn't reflected in the table view.
I could do a check in cellForRowAt to see if it's out of range before hand and return an empty cell if that's the case, but that seems terribly inefficient. Although it's might be a good idea regardless, in this case seems like such a band-aid fix.
So how can I solve this in an effective and efficient manner?
There are two solutions to resolve this issue.
once the data set is updated just call the reloadData on your tableView which will reload all the data.
if some data is deleted then use deleteRows(at:with:) method .
for single row deletion
data.remove(at: index)
self.tableView.deleteRows(at: [indexPath], with: .fade)
for multiple rows deletion with insertion
var indexPaths: [IndexPath] = []
for index in indexArray
data.remove(at: index)
indexPaths.append(IndexPath(item: index, section: 0))
}
if #available(iOS 11.0, *) {
self.tableView.performBatchUpdates({
self.tableView.deleteRows(at: indexPaths, with: .fade)
})
} else {
self.tableView.beginUpdates()
self.tableView.deleteRows(at: indexPaths, with: .fade)
self.tableView.endUpdates()
}
If you have multiple rows to be removed just create a array of index path with matching row index and pass it to the delete function.
Edit:
use batch updates only for multiple insert/delete/move operations only. as per the apple docs
UITableView defers any insertions of rows or sections until after it has handled the deletions/insertions of rows or sections. This order is followed regardless how the insertion and deletion method calls are ordered.
You can call tableView.reloadData in viewWillAppear method. So it will again reload full tableview with new data.
Removing and adding Collection view again with new data but still showing the old data. (Don't need reloading, just need to remove and add collection view again with new data)
Description:
The issue goes as follows:
I am using two Custom Layout Collection Views, reloading and paginating them both.
Invalidating layout, Reloading and Pagination goes smoothly, no issues in that.
But in case of switching perspective, say, from QuarterlyView to MonthlyView, i removed the whole collection view from parent view and again show it with new data.
This is where the problem arises, the new collection view is still showing the old data even after the previously inputed 2D array is replaced with a new one.
Please note, on switching perspective, I just need to remove and add the collection view with new data, not reloading at all.
This is exactly what I am doing in swift:
func onSwitchingPerspective(url: String) {
headerArray.removeAll() // REMOVED THE HEADER AND CONTENT ARRAY
contentArray.removeAll()
self.customHeaderCollectionView.removeFromSuperview() // REMOVED THE HEADER AND CONTENT COLLECTION VIEWS
self.customContentCollectionView.removeFromSuperview()
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { // NEED GLOBAL QUEUE TO CALL API AND LATER UPDATE UI ON MAIN THREAD
(self.headerArray, self.contentArray) = self.getEventsData(URL: url)
DispatchQueue.main.async {
self.showHeaderCollectionView() // ADDED THE COLLECTION VIEWS AGAIN
self.showContentCollectionView()
self.customContentCollectionView.collectionViewLayout.invalidateLayout() // CODE TO RELOAD BOTH THE COLLECTION VIEWS
self.customContentCollectionView.reloadData()
self.customContentCollectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
self.customHeaderCollectionView.collectionViewLayout.invalidateLayout()
self.customHeaderCollectionView.reloadData()
self.customHeaderCollectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
}
}
}
Without
self.collectionView.reloadData()
it shows only your previous data.. Its necessary to reload after your
headerArray.removeAll() // REMOVED THE HEADER AND CONTENT ARRAY
contentArray.removeAll()
I am new in Swift. The sample codes about UITableView show that new item is placed on the bottom of the list. How may we reverse this? I searched internet but could not find an answer.
Thanks.
Guven
UITableviews show data based on the order of an array such as an array of characters like
var data = ["B","C","D","E"]
Typically, you add data into array by using append which adds data at the end of the array, hence why it adds it at the bottom of the list.
data.append("A")
If you want your table view to add data on top of the list, then you can add your data at the beginning of the array like this.
data.insert("A", at: 0)
now reload your tableView, and new data would be added at the top of the list
yourTableViewName.reloadData()
To put new item on top, insert it at desired position (index 0) and reload corresponding indexPath (row: 0, section: 0).
let indexPathOfFirstRow = NSIndexPath.init(forRow: 0, inSection: 0)
yourArray.insert("some element", atIndex: 0)
tableView.beginUpdates()
tableView.insertRowsAtIndexPaths([indexPathOfFirstRow], withRowAnimation: .Automatic)
tableView.endUpdates()
Reloading whole table is a costly task and not recommended.
Add item to the array last index .
Then reload tableView
All you really need to do is to .insert your object at index 0 rather than append. Appending your latest value at the first index 0 ensures your last data is shown at the top. You can continue using tableview.reloaddata()as usual. Hope the screenshot helps
Inserting new items on data array at 0 index is easy but keeping the state of tableview as it is difficult especially when you are dealing with headers too...
I am using this to append old messages when paginating the chat...
you can iterate on data and insert new items and header with data.insert("new message", at: 0) but tableview automatically going to jump on zero index. to keep that state you should need a unique id in every new item in my case it was time and messageId
I am saving the last message id before appending new items ... and using that id to calculate the indexPath of the previous top message cell and scroll to it with no animation...
func getIndexPath(WithMessageId id: Double) -> IndexPath? {
for (section, sectionObjects) in messagesArray.enumerated() {
if let index = sectionObjects.Messages?.firstIndex(where: { $0.MessageID == id }) {
return IndexPath(row: index, section: section)
}
}
return nil
}
this method returns the indexpath use that as follows
self.tableView.reloadData()
self.tableView.scrollToRow(at: getIndexPath(WithMessageId: lastMessageID) ?? IndexPath(row: 0, section: 0),
at: .top, animated: false)
its gonna append new items without even any glitches like Twitter appending new news feeds on top...
I have a UICollectionView that is displaying data that has been saved locally to the device. Once this data is shown, I call to the server to retrieve any new data.
If the server has new data, I need to insert it at the beginning of the array datasource since it is the newest data. I then need to refresh the UICollectionView so that it knows new data has been inserted in the beginning / at the top.
This all works fine, but the problem is that an animation happens and it is noticeable to the user. If the user has scrolled down into some of the cached data, and then the new data is added to the top, the cells they are seeing on screen will reload/animate.
I need to keep all of the above just without the animation. The new cells should be added at the top without changing any of the current cells the user is viewing.
Here is the code I am using to insert and handle the new data:
self.collectionView.performBatchUpdates({
var index = 0
var indexPaths = [NSIndexPath]()
for newObject in objects {
// Update datasource
self.datasource.insert(newObject, atIndex: index)
// Create index paths for new objects
let indexPath = NSIndexPath(forItem: index, inSection: 0)
indexPaths.append(indexPath)
// Update index
index += 1
}
self.collectionView.insertItemsAtIndexPaths(indexPaths)
}, completion: { (completed) in
})
I have tried wrapping this code in a UIView animation block with a duration of 0, in a UIView.performWithoutAnimation function, and have also implemented the following in my UICollectionViewFlowLayout subclass:
public override func initialLayoutAttributesForAppearingItemAtIndexPath(itemIndexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
return nil
}
None of this works. The animation always happens. The cells the user is viewing flash / animate / etc. when the new data is inserted and added.
How can I accomplish this without an animation?
how is it possible to reload data for a cell in my table view? I want to do a request to my Server and the server responses JSON. At the moment I implemented the request and the response handling and I can show the data in my cells. But I want that the server only responses maybe ten datasets. When I scroll to the end of the table view with my ten cells in the app I want to do the next request to my server and the server delivers the next ten datasets and so on. I work with Swift as the programming language.
THX!
You can use self.tableView.reloadData() in order to reload your entire table or self.tableView.reloadRowsAtIndexPaths(paths, withRowAnimation: UITableViewRowAnimation.None) in order to reload a specific cell.
You can read more about the UITableView class here
To reload visible cells of tableview in Swift 3.0
pTableView.beginUpdates()
pTableView.reloadRows(at: pMessagesTableView.indexPathsForVisibleRows!, with: .none)
pTableView.endUpdates()
Try this...
Swift 2.0
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
Swift 3.0
DispatchQueue.main.async {
self.tableview.reloadData()
}
you can get Array of visible Cell by using TableView Function tableView.visibleRows() or You Can Get IndexPath of Visible Rows By tableView.indexPathsForVisibleRows() ! and then you can reload table by tableView.reloadData() Function!
look at following links
link1
you can read about UITableView Class Here