Redraw UITableView after updating data async - iphone

I have a UITableview that I load with data async so the tableview might appear without data.
I have tired the ReloadData method but the tableview remains empty until I scroll the tableview, suddenly the data appears.
The same thing happens when I load a tableview as a detailedview and switching between items, the previoud items data appears first and as soon as I scroll in the table view it shows the correct data.
My guess is that the ReloadData method works just fine, but I need to redraw the tableview somehow, any suggestions on how to solve this?
/Jimmy

You said you're populating content asynchronously but did you invoke the reloadData in the context of the main thread ? (and not via the thread that populates the content)
Objective-C
[yourUITableView performSelectorOnMainThread:#selector(reloadData)
withObject:nil
waitUntilDone:NO];
Swift
dispatch_async(dispatch_get_main_queue(), { self.tableView.reloadData() })
Monotouch
InvokeOnMainThread(() => this.TableView.ReloadData());

Yonels answer is perfect when your view is currently visible to the user (e.g: User presses a reload button which populates your UITableView.)
However, if your data is loaded asynchronously and your UITableView is not visible during the update (e.g: You add Data to your UITableView in another View and the UITableView is displayed later by userinput), simply override the UITableViewController's viewWillAppear method.
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.tableView reloadData];
}
The positive effect is that your UITableView only reloads it's data once when the user actually want's to see it, not when new items are added.

I had the similar issue today(Storyboard in iOS 7.0).
I was using table View inside a UIViewController.When the view was loaded everything was working fine; all the delegates were getting called.However ; When the underlying dataSource(in my case,Array) was modified; the visible table rows were not getting updated.They were updated; only when I was scrolling the table view.
Tried everything; calling reloadData on Main thread;calling reload in ViewWillAppear; nothing worked.
The issue that I found ; was that I had not made the connection in storyboard; for the table view with the reference Outlet.The dataSource and delegate were set though.
I did not think that it could be the issue; as everything was working fine at the first go.
Hope it helps someone.I had some terrible time to find this out.

I guess it wasn't reloaded.
When you scroll the cells to out of screen, then…
tableView: cellForRowAtIndexPath:
…will be called. So it will be reloaded.
I guess UITableView variable is not validated.
If you use UITableView as a main view, you can try this.
[self.view reloadData];
or
[self.tableView reloadData];

Swift 4:
DispatchQueue.main.async { self.tableView.reloadData() }

After somewhat naively copying in yonel's solution and calling it good I realized that calling performSelectorOnMainThread:withObject:waitUntilDone: fixed the symptom, but not the problem. The bigger problem is that you are making UI updates while still in the context of the asynchronous or background thread.
This is what my code looked like:
dispatch_queue_t queue = dispatch_queue_create("com.kyleclegg.myqueue", NULL);
dispatch_async(queue, ^{
// Make API call
// Retrieve data, parse JSON, update local properties
// Make a call to reload table data
});
When it should look like this:
dispatch_queue_t queue = dispatch_queue_create("com.kyleclegg.myqueue", NULL);
dispatch_async(queue, ^{
// Make API call
// Retrieve data, parse JSON, update local properties
dispatch_async(dispatch_get_main_queue(), ^{
// Now make the call to reload data and make any other UI updates
[self.tableView reloadData]
});
});
If the only thing you need to do is call [self.tableView reloadData] it's probably fine to use performSelectorOnMainThread:withObject:waitUntilDone: since it accomplishes the same goal, but you should also recognize what's happening in the big picture. Also if you are doing more UI work than just reloading the table then all of that code should go on the main queue as well.
Reference: A concise example of using GCD and managing the background vs. main thread.

Related

How do you know when the UITableView is done updating it's view?

So I implement UITableViewDataSource protocol using an NSFetchedResultsController.
I then modify the contents of the Core Data base and the NSFetchedResultsController then update the tableView..
Is there anyway to know when the tableView has done reloading the data?
We have a complex data model that is caching against a REST implementation, and it's difficult to determine if we need to get more data to fill up the screen (because the screen might be using complex filters against the raw data loaded). Also, the UITableViewCell objects are NOT guaranteed to be the same height.
The easy answer was to simply dump the data to core data, and use an NSFetchedResultsController to serve the data to the UITableView.
Here is how it works:
We added a tableview.tableFooterView that displays our "data loading" message.
Loading up a batch of data from the REST API and then using it update our Core Data objects.
This triggers the controllerDidChangeContent method to trigger, which then triggers a tableView reloadData (eg:)
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView reloadData];
}
We then basically then "Check" to see if the Footer is still visible 250ms later by calling a method like:
[self performSelector:#selector(checkIfFooterViewIsVisible) withObject:nil afterDelay:0.25];
- (void)checkIfFooterViewIsVisible
{
BOOL viewVisible = CGRectIntersectsRect(self.tableView.bounds,self.tableView.tableFooterView.frame);
if (viewVisible)
{
[self getMoreData];
}
We also check to see if the Footer is in view everytime we scroll.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self checkIfFooterViewIsVisible];
}
If we load the "last batch" of data, we actually just remove the tableViewFooter view object from the table (so it can't ever be visible).
So the answer is cool, because we can figure out if we need to pull more data, only if the user "needs" it. Either because there wasn't much visible data in the first batch, or because they have scrolled down and want more data. Checking the current placement of the tableFooterView lets us know if we have "painted" enough data on the screen.
The PROBLEM is - can I get rid of the:
[self performSelector:#selector(checkIfFooterViewIsVisible) withObject:nil afterDelay:0.25];
If we set the delay too fast, then UITableView doesn't have time to update the screen (and change the footer's position). If we update too slowly, then it feels like the app "stutters" as it loads data, and can load data slower than it could. But different iOS devices (and different network coverage) are going to mean slightly different timings, so the "let the UITableView update and check a bit later" works most of the time, but I feel like this could work smoother.
Is there anyway (maybe by overloading UITableView) that we can determine that the UITableView is "done" loading UITableViewCell objects? (at least until the next time the scroll moves?). Adding in this weird delay works well, but it would work cleaner if we knew definitively that the [tableview reloadDate] operation has completed.

Refresh/Reload UITableView on every callback

In my iOS5 application, I have UITableView that would display cells based on NSString values stored in progressLabelArray. I have registered a call back from my lower layers and I receive them in a C function from where I manage to call the UI update method through a reference to self.
static void callback_handler(int nCode) {
[refToSelf updateProgressView:nCode];
}
-(void) updateProgressView:(int32_t) nCode
{
NSString *status = nil;
status = [self progressUpdateToString:nCode];
[self.progressLabelArray insertObject:status atIndex:0];
[self.progressTableView reloadData];
[self.progressTableView setNeedsDisplay];
}
I guess, since the callbacks are coming on the same (UI thread), performSelectorOnMainThread may not be required.
My problem is that the table view only gets refreshed when the entire operation is complete, showing the last call back values. Is there a way by which I can force the UI to refresh after every callback? The callbacks coming might be relatively fast.
Resolved the issue by running the progress code in a separate thread. The callbacks come on a seprate thread and there I update my main thread (UI thread). I guess the UI thread was not getting any cycles to update the UI.
Put your
tableView.reloadData()
call in the following block and your tableview will refresh without any issue and without refreshing the scroller like in realtime apps.
For swift 3:
DispatchQueue.main.async()
{
tableView.reloadData()
}
For Objective C:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Background work
[tableView reloadData];
});

iphone - scrollRectToVisible issue

This functions perfectly, but I want to make it an once-function, not fixed-function. When I change tableview with other data, the data displays at the index from previous tableview. So my solution to this is implementing the code below. When I implement this, it works, but when I scroll down, it scrolls up all the time, so it is virtually impossible to scroll down further. Any idea how to make it performs only once when I change tableview?
The code:
[tableView scrollRectToVisible:CGRectMake(0,0,1,1) animated:NO];
Edit 21 august:
Guys, thank you very much! The code was in cellforrowatindexpath. I moved it to inside the function which changes tableview and it works like a charm :D
You could override the reloadData method if that is how you are reloading the Table View with new data and put the code in there. Something like this in your table view controller should suffice:
- (void)reloadData {
[super reloadData];
[tableView scrollRectToVisible:CGRectMake(0,0,1,1) animated:NO];
}
If it's scrolling up every time you scroll down, I assume you put the code in the cellForRowAtIndexPath method which will get called every time you want to present the cell. This is not the correct place to put that code.
It IS a once-function. Most probably, this code of yours is executing again & again. If you have kept this in a function such as cellForRowAtIndexPath:, which is called frequently, that may be the cause of this problem. Where have you put it?
HTH,
Akshay

How to "refresh" UIView

I have data being refreshed in a modalViewController and when that data gets refreshed, the parent controller needs to refresh its data as well. I tried doing a [tableView reloadData]; but it didn't work properly since the actual array values aren't being refreshed. Is there a way for me to reload a controller without the user seeing any animation?
Thanks for any help!
Are you refreshing the data off the main thread? If so, you need to call the reloadData method using the following method:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
So for a tableView it would be something like:
[tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
The underlying view can get released while the modal view is showing, so you shouldn't try to modify it directly. Instead, you can use the viewWillAppear: method on the main view controller to make any necessary updates.
I'm not sure what you mean that "the actual array values aren't being refreshed". How the two view controllers exchange data depends a lot on what you are trying to accomplish. If the modal view is only used in one place, the main controller can access its properties directly. Or maybe you want a dedicated class to hold the state... Again, depends on the context.

Refreshing a UITableView

I have a UITableView subclass and a UITableViewCell subclass that I'm using for cells. I'm bulding all my cells in advance and store them in an array from where I use them in cellForRowAtIndexPath. Aside from this I have a thread that loads some images in each cell, in the background. The problem is that the cells don't get refreshed as fast as the images are loaded. For example, if I don't scroll my table view, the first cells only get refreshed when all cells have been modified and the thread has exited.
Any ideas on how I can effectively refresh my tableview/cell?
Have you tried calling [cell setNeedsDisplay] but on the main thread?
setNeedsDisplay when called on a background thread does pretty much nothing,
try this:
[cell performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:nil waitUntilDone:NO];
Are you using a callback to notify the controller of your tableview that the images have been loaded? If not, that would be an ideal method.
When the image loads, fire off a callback to the table view controller that sets the image on the cell, and then calls reloadData on the tableView.
This way whenever a new image loads, the table will update to display it.
Not sure exactly what you are trying to achieve with the images - but can I guess they are coming from a server and that is why you want to download them in another thread?
I would not try to load up the cells before you display the table - you should use lazy loading as much as possible to make sure you are making the most of the memory on a device.
My suggestion would be to look at using a subclass of NSOperation to manage the loading of images. Firstly NSOperation will handle all the complexity of threading for you and allow you to queue up the operations. You will then be able to prioritise the operations that you want completed for the cells at the top.
As each operation completes you can make a call back to the cell or tableViewController (perhaps create a delegate protocol to make this really easy).
If you have an operation per image/cell combination then you should be able to refresh each cell as the operation completes. Doing this along with prioritising the operations will give you an optimal solution.
If the NSOperations sound complex or you are put off by this - please do try it - it is a lot simpler than I might have made it sound.
Have you tried calling [cell setNeedsDisplay]?