UITableView reloadData blocks UI with many updates per second - ios

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

Related

Odd behaviour after changes to IOS8 for UICollectionView and UIAnimation

I started porting my app to IOS8 recently and have observed a change of behaviours in both UICollectionView and UIAnimation.
In short, my app is a game using a collection view for displaying pictures and runs in parallel couple of "background" animations. When user press on a cell, the idea is to simulate a "push" button, the same user should be able to push several cells one after the other rapidly. In IOS7 and earlier version, everything was smooth.
However, since IOS8, when the user press one cell, he/she has to wait for the push animation to end prior to pressing on the next one.
At first i look into the changes from UICollectionView and UIAnimation in IOS8 and could find mostly two changes, one deals with the "auto-sizing" of cells; and the second one deals with the concept of additive animations.
After i started looking into the refresh function of the UICollectionView. Due to a bug in IOS7 , the classical call "reload data" didn't work and should have been replaced by the following:
NSMutableArray *indexPaths = [[NSMutableArray alloc] init];
for (int i = 0; i < [self.assets count]; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
[indexPaths addObject:indexPath];
}
[collectionView reloadItemsAtIndexPaths:indexPaths];
The code above works fine in IOS7 version. However, i realised that it is the call which actually make the push animation slower in IOS8. If it is simply removed, indeed the graphical update doesn't have (e.g. change of the cell colour) but the animations become fluid again and user can press one cell after the one rapidly and the push animations will occur.
Replacing the above code by ReloadData gains the change of colour when the user press on the cell, but lose the push animations. Also, an interesting behaviour comes from the code below:
WizzPlayViewCell *cell = (WizzPlayViewCell *)[collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:(position) inSection:0]];
[WizzAnimationManager pulseOnce:cell.foregroundCellImage toSize:scaleFactor withDuration:durationAnimation];
If i keep the old code ([collectionView reloadItemsAtIndexPaths:indexPaths]...) , the cell retrieves from the cellForItemAtIndexPath is the right one; therefore the push animation method can execute it. However, if i use the ReloadData method, the cell is null.
This is as far i got and i am really puzzled by the changes of IOS and do not see at the moment the relations between all misbehaviours.
Would you:
have met similar issues when porting to IOS8 with UICollectionView or UIAnimation ? and how did you overcome them?
have a view why the reloadItemsAtIndexPaths:indexPaths code is now blocking the main thread and do not allow to execute the next push animations?
have some opinion why the cell is null when using Reloaddata instead?
have some extra-ordinary approach to help addressing this issue ? :)
Thanks all in advance for experience and insights you may have .

Smooth UITableView scrolling while calling reloadData

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?

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 table cells with fixed images

I'm developing an ios app and am trying to achieve an effect in a tableview like the following website: http://www.poormet.com/
There is a couple of ways I have thought about this. I know you can set the background of the tableview which remains fixed and then you could possibly swap the images as the text box covers the image. This is limited though as you can only have one image on the screen at a time which is not what I'm after.
I also thought about pushing the image down for each cell as the cell moves up, but for this case I couldn't set the frames position in the scrollViewDidScroll method(which I used to obtain the frames actual position in the tableview).
I was also wondering if there was anything similar in ios to what you have with div's in html where you can specify an image to be fixed.
How would I go about doing this? If you guys need any more information let me know, I'm new to posting on this site.
Thanks.
I think you should be able to do this through scrollViewDidScroll. I'm guessing you already have subclassed UITableViewCell and made your own, so heres a thought:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
for(UITableViewCell *cell in [yourTableView visibleCells])
{
//I haven't tried this `convertRect` method, and the views might be in the
//wrong order, but I'm sure you'll figure it out. It should solve this.
CGRect position = [yourTableView convertRect:cell.frame toView:self.view]
[((YourCustomCell*)cell) updateImage:position];
}
}
Then in your custom cell class, add a method named -(void)updateImage:(CGRect)position; which will now contain the location of the cell, and you can move the image whatever way you want, as I'm guessing you already have a method for. I.E:
-(void)updateImage:(CGRect)position
{
//change the location of the image inside self.imageView here
}

iOS: Tapping on UITableView does not call didSelectRowAtIndexPath

My app uses a UITableView with a UINavigationController to show a more detailed view when a row of the table is tapped - the basic drill-down routine.
When I tap on a row, it is highlighted, but the delegate methods tableView:willSelectRowAtIndexPath: or tableView:didSelectRowAtIndexPath: are not called (verified using debugger).
Now here's the weird part:
There are some other table views in the app (they don't drill down) and none of them exhibit the issue.
If I tap the row rapidly and repeatedly, after many tries (10 - 20 is normal), tableView:willSelectRowAtIndexPath: and tableView:didSelectRowAtIndexPath: are called and processing continues normally.
The problem occurs only on an (any, actually) iPad running iOS 6. It works fine with iPads running iOS 5, or with any iPhone running any iOS version, 6. It also works with the iPad simulator using iOS 5 or 6.
So it seems that something is receiving the tap before the delegate methods are called. But what?
I not using any UITapGestureRecognizer, so that is not the issue.
I am not using multiple UITableViewControllers for the table, so this is also not the issue.
I was having this issue with an app when running in iOS6. The tableView:didSelectRowAtIndexPath: method was never being triggered, even though it was working before updating to iOS6.
I finally figured it out by looking at the xib and the attribute inspector for the table. In the Table View section I had the Selection option set to No Selection and Show Selection on Touch was not selected.
Changing the Selection option to Single Selection actually got this working for me again and leaving the Show Selection on Touch unselected means that I don't see any flash or change of colour when the row is selected.
I encountered a similar issue. iOS 6 appears to be much more sensitive to the slightest upward movement on a tap on a table view cell. It appears it is registering the slightest movement as a scroll request rather than a cell selection. My table view was set to scrollEnabled = NO because it is a table view with static selection values. The table view appears within a small area of a larger iPad view. If I carefully tap a cell and lift directly up, the cell is selected.
To resolve it, I changed scrollEnabled to YES and made sure the area dedicated to my tableView was larger than the actual area needed to show the table view, thus preventing it from scrolling. Not sure if this is is the cause of the issue you are experiencing, but hope it helps.
I had the same problem. The UITableView was not triggering the didSelectRowAtIndexPath method when running in iOS6, but it was working fine in iOS5.1.
I had also implemented the
- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath{
return NO;
}
and returned NO. In iOS5 it was all working fine, but the minute I switched to iOS6 it would cease to trigger the didSelectRowAtIndexPath. Once I removed that shouldHighlightRow method, everything worked in iOS6 again. Cheers!
The usual reason that didSeletRowAtIndexPath() doesn't get called is having a UITapGestureRecognizer on your viewcontroller with Cancels touches in view set to YES.
The correct and only sensible fix is to set Cancels touches in view to NO. Then your table row selection should work nicely.
Obviously this has some implications in your gesture handlers - you may need to add some code to your gesture handler to stop some touches going on to your views i.e.
- (IBAction)onTapGesture:(UITapGestureRecognizer *)sender {
if (sender.view == someOldViewThatINeedToDealWith) {
sender.cancelsTouchesInView = true;
}
}
I tried all the other answers posted, and although they seemed promising, they didn't solve the problem.
I've since discovered the cause of problem in my case: I was calling [self.tableView reloadData] on a different thread (due to handling an NSNotification event posted from a background worker thread).
I fixed the problem by changing [self.tableView reloadData] to
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
Hope this helps.
The other suspect could be a Tap Gesture Recognizer set to the view controller's view swallowing all taps like a blackhole. Remove the gesture recognizer will enable did select. I just had the same issue and the tap gesture was the cause.
The resolution is really weird: the UITableViewController registered for all notifications. In the handler for the notifications, the table view data was being reloaded using
[self.tableView reloadData]
According to Apple DTS, when the table reloads data, this causes a notification to be sent to the observer, causing a race condition ...
No explanation for why it would work sometimes or why it would always work on an iPhone. But registering only for a small subset of notifications (i.e. the ones I was really interested in) fixed the problem!
Setting
recognizer.cancelsTouchesInView = NO;
takes care of it if your use case allows simultaneous handling
of touches by [tap] gesture recognizer (kdb dismiss for example)
and the view the recognizer is attached to.
in my case I hade button which received all touch events before tableviewcell

Resources