How to update, not reload, UITableView? - ios

My question may be looks simple, but it doesn't work. I searched a lot and try a lot of methods, but it doesn't want work!
So, I have an UITableView. In the background one of my functions work and detect, if a new message has been received, it writes this value into Core Data and I want to add badges on my cells when I receive a new message.
But when I try this:
func reloadTableView() {
dispatch_async(dispatch_get_main_queue(),{
self.tableView.reloadData()
})
}
it doesn't update my tableView and doesn't show new badges. For this, I need:
Change my controller and return to it
Or I need drag my tableView to top and leave it
in these cases it will show my badges.
So, how can I update my tableView to show new added badges without described above 2 methods?
UPDATE
UPDATE 2
I have 2 files: ContactsTableViewController and Messages.swift.
In my Messages.swift I handle new messages and when I receive a new message I get in logs:
print("New message for \(user.jidStr)")
ContactsTableViewController().reloadTableView()
In my ContactsTableViewController:
func reloadTableView() {
self.tableView.reloadData()
print("updated")
}
It reloads my tableView, as I get updated message in logs, but it doesn't updated it and my badges

Try to update only cells that receive new message with:
self.tableView.beginUpdates()
self.tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: row, inSection: section)], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
where row - the row of your cell in it section that receive the message

Example in Objective-C:
[_tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
Example in Swift 2.0:
self.tableView.reloadRowsAtIndexPaths([NSIndexSet, indexSetWithIndex:0], withRowAnimation: UITableViewRowAnimation.Automatic)

Did you try the NSOperationQeue?
func reloadTableView() {
NSOperationQueue.mainQueue().addOperationWithBlock {
self.tableView.reloadData()
})
}
Edit (because I can't comment)
override func viewDidLoad() { NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "reloadTableView", userInfo: nil, repeats: true) }
If you are using something like this you call the update function every second and this doesn't make sense. You should call it after getting your data

The reloadData should work... ( I think it is better to call it on the main thread. )
Have you checked that your objects array is correct ? If it comes from a CoreData request, it may use the cache. Be sure that you have synchronized the CoreDataContext ( by calling the save method on the context after your insertion ) and that your fetch returns the last created objects.
Just a guess…

Related

Keep timestamp label updated in UITableView

I have a UIViewController that has a UITableView which presents comments fetched form a live Firebase database.
Every time a new comment arrives, I call
tableView.beginUpdates()
tableView.insertRows(at: [IndexPath(row: self.liveComments.count-1, section: 0)], with: .fade)
tableView.endUpdates()
to insert the latest comment with a fade animation. This works fine.
However, each cell has a label that shows when it was posted, in the form of "seconds, minutes or hours ago". The problem is that when many comments arrive, the age label does not get updated, since the existing cells are not updated, and it looks to the user like the comment ages are wrong.
I've tried calling
tableView.reloadRows(at: self.tableView.indexPathsForVisibleRows ?? [], with: .none)
inside my tableView updated block, but the animation is all messed up, since all of the visible cells seem to get animated in a weird, "jumpy" way.
I've also tried getting all of the visible cells, and calling a method on them to update their timestamp labels manually, but I get a crash when I do this, so I guess it's not recommended:
if let visibleCells = self.tableView.visibleCells as? [LiveCommentTableViewCell] {
visibleCells.forEach { cell in
cell.updateCommentAgeLabel()
}
How can I approach this? I just need to reload all visible cells without an animation, and the last cell with a fade in animation. Thank you!
I would just reload all the data, as long the cellForRowAt sets the timestamp label correctly it should work fine:
// still do your nice animation
tableView.beginUpdates()
tableView.insertRows(at: [IndexPath(row: self.liveComments.count-1, section: 0)], with: .fade)
tableView.endUpdates()
// now just refresh the entire table
tableView.reloadData()
of course you're going to want to make sure that whatever collection feeds the numberOfItemsInSection is also updated before calling reloadData() im assuming you're already doing this as well or you'd be running into a lot of bugs and crashes
make sure that code that edits UI is on the main thread too, obviously.
That being said what does your cell.updateCommentAgeLabel() function look like bc that would work in theory as well unless potentially its not being called on the main thread again or the cast isn't working.
Perhaps try telling the system you want it to do a layout pass:
if let visibleCells = self.tableView.visibleCells as? [LiveCommentTableViewCell] {
visibleCells.forEach { cell in
cell.updateCommentAgeLabel()
cell.layoutIfNeeded() // either this
}
tableView.layoutIfNeeded() // OR this at the end, I dont expect you'll need to do both but not sure if both work

Realm observe with UICollectionView - race conditions

I am using Realm as a caching layer so that whenever data is presented to the user, it is first fetched from the database and displayed to the user. Subsequently, a server request is sent to fetch the newest version of the data, sync it with the Realm database and display the changes in a UICollectionView.
The problem is that when the cached data is retrieved from the Realm database and the UICollectionView is getting updated, there is a chance that the server request for update finished before the UICollectionView loaded all the cells and since the Results list is a live collection of data, it could have been modified. Now for example, if an item was removed on the server-side, the live collection would hold one item less and therefore cause out of bounds exception.
This being said, even the code provided in official Realm documentation is not thread-safe considering the fact that the results can be changed while the UITableView is asking for each row one by one:
class ViewController: UITableViewController {
var notificationToken: NotificationToken? = nil
override func viewDidLoad() {
super.viewDidLoad()
let realm = try! Realm()
let results = realm.objects(Person.self).filter("age > 5")
// Observe Results Notifications
notificationToken = results.observe { [weak self] (changes: RealmCollectionChange) in
guard let tableView = self?.tableView else { return }
switch changes {
case .initial:
// Results are now populated and can be accessed without blocking the UI
tableView.reloadData()
case .update(_, let deletions, let insertions, let modifications):
// Query results have changed, so apply them to the UITableView
tableView.beginUpdates()
tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
with: .automatic)
tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.endUpdates()
case .error(let error):
// An error occurred while opening the Realm file on the background worker thread
fatalError("\(error)")
}
}
}
deinit {
notificationToken?.invalidate()
}
}
The only way I can think of fixing this is to create a deep copy of the results as well as synchronize the body of the observe function using Semaphore or similar to make sure the data will not get in an inconsistent state, which I consider very inefficient. (Note that tableView.endUpdates() does not mean the UITableView has reloaded all the data, however it is just dispatched to a queue and ready to be processed in async.)
I would like to hear any suggestions how to implement this in an efficient way such that the mentioned race conditions are eliminated.
You need to do all of your UI updates on the main thread. If you do this an the first sets of results updates the collection view on the main thread, when the next set of results also comes it it will be queued on the main thread so it updates after the first set is done.
Based on:
The problem is that when the cached data is retrieved from the Realm database and the UICollectionView is getting updated, there is a chance that the server request for update finished before the UICollectionView loaded all the cells and since the Results list is a live collection of data, it could have been modified.
I do not think that that will happen, since as soon as your live collection will be changed update notification will be triggered and collection will be rebuild/updated accordingly. However, as I said you in PM it was some time ago when I worked with realm.
It is quite easy to test your hypothesis: decrease speed of your simulator's internet, or make huge table, etc. I am really curious if you can actually create a problem which you think you will have.

Reloading data from a tableView within a collectionViewCell

I have a tableView inside a collectionViewCell and I get an error when I try to reload the data.
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3600.6.21/UITableView.m:1610
I tried using Dispatch.main.async and it seems to get rid of the problem. The only thing is that it doesn't reload the data and nothing changes in the tableView
func cleanItems(completion: #escaping (Bool) -> Void) {
Dispatch.main.async {
cell.tableView.beginUpdates()
// Make changes in the Data Source
cell.tableView.deleteRows(at: selectedItems, with: .fade)
cell.tableView.endUpdates()
// Reloading sections accordingly depending on what the user has deleted
// Do I need to reload data here again? It used to work without Dispatch, but it wasn't stable
cell.tableView.reloadData()
// Updating items with if statements to reload data in Firebase
completion(true)
}
}
This doesn't reload the data at all and nothing seems to change. The good thing is that I don't get a random crash, which was the case before implementing Dispatch.main.async
I've retrieved the numberOfRows in each section to see how many rows there are after ending updates.
print(cell.tableView.numberOfRows(inSection: 1))
and I get the same number of rows that are in the current view.
This is crucial, because if the tableView sections are all zero, the collectionViewCell should disappear. And we never get here in the completion block as it says that the numberOfRows has never changed. Leaving us with a non updated tableView.
I solved this by moving Dispatch.main.async outside the function call.
Dispatch.main.async {
cleanItems(completion: { (success) in
etc.
})
}

Reloading whole ViewController after custom Button pressed in TableView

I added 2 UIButton in my custom UITableViewCell.
When pressed something is done with the displayed object(in this case a User). Now I want the row/cell to disappear. My Idea was to reload the screen via triggering the viewDidLoad() and viewDidAppear() functions,
since i use PFQueries to obtain user data and display them in my table view.
What happens is, that other than deleting that row since my query shouldn't find the data, it just adds the same things again.
Is there a better way to solve this? I want to delete the row and redo my Query.
To delete a row from a table, you can use a function like this:
// Add this function to your ViewController
func tableDeleteRow(indexPath: NSIndexPath) {
// IMPLEMENT ME:
// first, remove the item from the data that drives the tableView.
// This is what I do. Yours will be different.
// self.tableData.removeAtIndex(indexPath.row)
// tell the table to delete the row
dispatch_async(dispatch_get_main_queue()) {
// Code
print("remove from table")
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
If you are looking to reload the entire table and redo the data you got from the server, do this:
// This is pseudo code.
func tableRefresh() {
// this is a pseudo code function.
// replace it with your own.
get_data_from_server() {
(data, response, error) in
// do something with data
dispatch_async(dispatch_get_main_queue()) {
print("refresh table")
self.tableView.reloadData()
}
}
}

reload cell data in table view with Swift

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

Resources