Smooth UITableView scrolling while calling reloadData - ios

Im using a method to get new item from web after user scrolls end of my UITableView.
when user hit end of list im calling my fetching class inside :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
to avoid ui freeze and get,parse new item from Serve r.
there is no lag here , but when i try reload data inside my table and adding item to
list (reladData) there is a minor hiccup is scrolling. its about 0.4 or 0.5 second and also
scrolling will be freezed during this time.
i even tried to use this method :
dispatch_async(dispatch_get_main_queue(), ^(void)
{
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES]
instead of normal reloadData but lag still apear.
is there anyway to avoid this , or should i create Message/progress animation or something
else to notify user during this lag like "please wait" pop up?

Related

UITableView reloadData blocks UI with many updates per second

I have an app that has the ability to download certain files. These files are shown in a UITableView, and when tapped, they begin to download.
I would like to show the progress of the downloads in the cell (for example, as a percentage). My code currently fires notifications when the download progress is updated.
The problem is that these progress updates are fired many (hundreds?) times per second. If I use [tableView reloadData], or even [tableView reloadRowsAtIndexPaths:...], the UI lags like crazy once the download starts, because this is being called so frequently.
My code is very minimal other than this. I'm not using any uncommon UITableView delegate methods, and I've tried to eliminate everything other than updating the 'download' label in cellForRowAtIndexPath: - but it still lags a lot!
What are my options? I could potentially create a timer that updates the UI every second or so in order to show download progress, but this seems hacky. The UI could be so much better! Is there any way that I can have my download label update instantly without lagging my UI?
It sounds like you need to slow down the flow of notifications.
Assuming progress is between 0 and 1, in the method that fires the notification, use something similar to:
if (floorf(progress*100) == progress*100) {
// send notification
}
This would only send a notification to refresh the cell when the progress if 1%, 2% etc.
In addition, in the UITableView, try to only reload visible cells:
if ([[self.tableView indexPathsForVisibleRows] containsObject:indexPath]) {
[self.tableView reloadRowsAtIndexPath:#[ indexPath ]];
}
I would use the performSelector:withObject:afterDelay method, which would look something like this
[self performSelector:#selector(updateCell:) withObject:cell afterDelay:0.5];
The updateCell method would look something like this
- (void)updateCell:(CustomUITableViewCell *)cell {
[cell setNeedsLayout];
if (cell && cell.downloadPercentage < 100) {
[self updateCell:cell];
}
}
What this is doing is calling the updateCell method every 1/2 second until the download is complete. It calls [cell setNeedsLayout] (or you could call whatever method you want to update your cell's display to update the percentage bar, like [cell setNeedsDisplay] or [cell updateDownloadLabel]) to update the cell, then calls updateCell again.
You can play around with the delay to get it looking and feeling how you want and balance performance.
There may be some issue with this getting called after a cell is scrolled off the screen, so make sure to check the cell still exists

Scroll and add Search Bar asynchronously

I have this application in which I have a right bar button item which adds the search bar as title view. This works fine but when the table view is scrolling, this doesn't happen. It only adds when the table view has stopped scrolling.
Any way to handle this?
The main thread does not responds to events if its busy, use GCD to add this to Queue
Check if this function is called while scrolling. If yes , then simply using GCD will solve your problem.
-(void)userTapOnRightBarButton{
NSLog(#"Main thread response to touch during scrolling"):
dispatch_async(dispatch_get_main_queue(), ^{
[self addSearchBar];
});
}

multiple countdown timers in uitableview

I'm developing a native application clone of Snapchat or Facebook Poke.
I have a few issues related to timers and tap&hold functionality in received messages.
I have a viewcontroller (ViewControllerIncomingMessage) with a tableview which shows received messages and a UILongPressGestureRecognizer that opens a detail view (starts timer and update a countdown label. When the long press gesture ends I dismiss the view and schedule every second the following timer:
- (void)timerFired:(NSTimer *)timer {
[self.tableView reloadData];
}
This actually works but prevents rows from being deleted (because of reloadData of table)
Anyone has already implemented a functionality like that?
Please could you give me any hint about implementing timer update in the best way.

ios activity indicator is visible once, and then will not show until I try again a minute later

Ok, so I have this piece of code:
- (void) searchBarSearchButtonClicked:(UISearchBar *)aSearchBar
{
[_activity startAnimating];
[self.searchBar bringSubviewToFront:_activity];
[self.searchBar resignFirstResponder];
if([_activity isAnimating]){
NSLog(#"its animating!");
}
dispatch_queue_t background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(background, ^{
[self performSelectorOnMainThread:#selector(filter:) withObject:aSearchBar.text waitUntilDone:YES];
dispatch_async(dispatch_get_main_queue(), ^{
[_activity stopAnimating];
if(![_activity isAnimating]){
NSLog(#"its not animating anymore!");
}
[self.tableView reloadData];
});
});
}
what i'm trying to do is start an activity indicator which I added as a subview to the search bar. In this code, I start the animation (logging for test) on the main thread, then set up a background asynchronous thread which runs the method that generates the new data set which will reload the tableview. I wait for this to return, and which point, I stop animation and reload the tableview on the main thread.
the result is this...when the application runs, I go to the table view, and I type a word in the search bar. When I hit enter, the activity indicator shows up, the table loads, and the activity indicator stops. This is great.
Then, I click on the search bar again, start a new search, only this time, the activity indicator does not show itself, but the NSLog shows that it IS animating. The tableview reloads as expected, but no activity indicator on the UI, but based on the NSLog, it seems to working.
Finally, if I wait about a minute, and search again, it works fine. What I'm suspecting is that that the background thread is not completing maybe, and the main thread continues doing what it needs to do.
Honestly, I've been beating my head for two days now, can someone explain what I'm doing wrong?
There's a chance that this has something to do with the view hierarchy, depending on where you added the UIActivityIndicatorView to. If you added it directly to the search bar, it might reorganize the subviews in a way you don't expect (and move the activity indicator behind some of them, blocking it from being visible, even though it's animating.)
A good option might be to embed the search bar within a toolbar, and then have the activity indicator on the left (or right) with flexible space separating it from the search bar.

iOS: Presenting a spinning wheel while NSFetchResultsController is performing a fetch

I am using a NSFetchedResultsController to populate a UITableView. The fetch take some time so I would like to present a spinning wheel to the user while the fetch is under way.
How would I do that?
You should start your spinner in the Main thread and push the "heavy work" for a secondary thread. When the work is done, stop the spinner. You can achieve that with something like this:
// Start the spinning here.
[mySpinner startAnimating];
// Declare the queue
dispatch_queue_t workingQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// it's not, so we will start a background process to calculate it and not block the UI
dispatch_async(workingQueue,
^{
// Some heavy work here.
dispatch_async(dispatch_get_main_queue(), ^{
// stop the spinner here
[mySpinner stopAnimating];
});
});
Doing the following in the Main thread wont make you accomplish what you want:
Start Spinner => Heavy work => Stop Spinner
When the Heavy work begins, it will block your UI thread, so you won't actually see the UIActivityMonitor animating.
To finish I would advise you using this as spinner.
You have several way to do that :
had a UIBarItem if you use UINavigationBar and set customView to UIActivityIndicator at the beginning of your function and hide it a the end.
Create a "modal" view with a UIActivityIndicator at the center of the view, and add this subview to your table view and remove it a the end of your function.
formally just you can use the UIActivityIndicatorView to present the wheel and for some good and delicate way use the MBHoods from here download the demo app here

Resources