Odd behaviour after changes to IOS8 for UICollectionView and UIAnimation - ios

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 .

Related

KIF cannot tap UIButton inside UITableViewCell if it's its UITableView's only one

I am having trouble automating tapping on an UIButton that is embedded inside a UITableViewCell if that cell is the table's only one. This is in the context of UI automation with KIF.
Here my relavant call:
[tester tapViewWithAccessibilityLabel: #"LABEL"
traits: UIAccessibilityTraitButton];
// trait only specified for 2nd case below
Here is what I am observing:
If I put the accessibility label on the UITableViewCell KIF's KIFUITestActor - waitForAccessibilityElement:view:withLabel:traits:tappable: returns the UITableView, not the cell. Somehow the table seems to inherit its only child's accessibility label and lets KIF encounter it first during its recursive search.
If I put the accessibility label on the UIButton instead, KIF finds it but determines that is is not tappable (i.e. UIView-KIFAdditions -tappablePointInRect: returns NO), presumably because its mainly transparent between the thin font lines for the button's label (the tap goes to a UITableViewCellContentView instead).
A workaround might be tapping on the row by it's NSIndexPath but maybe there is still a better way to overcome the described hurdles I am facing. So how could I instruct KIF to tap a button like this with a call to tapView...?
If you are making cell in code, make sure your button is added to cell.contentView.
If you are loading cell from xib, try to send contentView to back of view hierarchy (I have not found any downsides yet for doing it) with:
[self sendSubviewToBack:self.contentView];
This is just to confirm that this workaround is applicable:
[tester tapRowAtIndexPath: [NSIndexPath indexPathForRow: 0 inSection: 0]
inTableViewWithAccessibilityIdentifier: #"IDENTIFIER"];
The "good" solution should be something like this:
UITableViewCell *cell = [tester waitForCellAtIndexPath:indexPath inTableViewWithAccessibilityIdentifier:tableAccessibilityIdentifier];
UIAccessibilityElement *element = [cell accessibilityElementWithLabel:accessibilityLabel traits:UIAccessibilityTraitButton];
[tester tapAccessibilityElement:element inView:cell];
Unfortunately it doesn't work as expected. Sometimes the element is a UIAccessibilityElementMockView and tapping it in step 3 leads means just tapping the underlying cell. Naturally the results will not be as expected.
I've managed to finally work around that by doing something more along the lines of:
MyCustomCellClass *cell = (MyCustomCellClass *)[self waitForCellAtIndexPath:indexPath inTableViewWithAccessibilityIdentifier:tableAccessibilityIdentifier];
[cell.buttonOutlet sendActionsForControlEvents:UIControlEventTouchUpInside];
Note that this means
relying less on UI interaction
having a custom class
having an outlet to the button
But hey, KIF isn't supported by Apple so it sometimes, you know, breaks :)

Transitioning from one UIPopoverController to another is slow on iOS8

My app has multiple buttons each of which brings up a different UIPopoverController. We have 'passthroughViews' set so the buttons are still enabled while the popovers are up. Pressing one of the buttons while a popover is displayed dismisses the current popover and brings up the new one. The trouble is, this seems very slow on iOS8. The popovers come up and go away on their own just fine -- it's just when we switch from one to another that there is a pause between when the first one closes and next one starts to show. I'm calling these dismissPopoverAnimated and presentPopoverFromRect: calls back to back, so there is nothing going on between the two. I've tried setting 'animated' to NO for both of these but the pause still remains. Any help is greatly appreciated.
Rather than close the popover, re-purpose the same popover when the 2nd button is pressed. Move the location of the popover, on the screen and load the new content into it. Seems like that would be quicker and avoid whatever contention or latency issues you are experiencing switching from one modal view to the next (I suspect that is what the issue is). You'd lose the disappear/reappear animations, but it should be near instantaneous and provide a good user experience, because users don't really (in the long run) want to wait through animations to see their content when they push a new button anyway.
What's inside these popovers? If they're tied to something running on the main thread, then you are likely seeing that as your lag element. You might want to lean up your viewDidLoad and viewWillAppear methods. Try running largish processes on a background thread and updating it after appearing.
If you are using a core data store then you are likely using the primary context, which always runs on the main thread. Try caching those calls ahead of time, or run your fetches on a child thread and returning those to the main thread after the popover has loaded.
Try this code,
[UIView transitionWithView:pop1.contentViewController.view duration:0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
[pop1 dismissPopoverAnimated:NO];
} completion:^(BOOL finished) {
[pop2 presentPopoverFromRect:btn.bounds inView:self.view permittedArrowDirections:UIPopoverArrowDirectionLeft animated:NO];
}];
hope it helps.
I had a similar issue that had my head banging on the problem for almost a day: the UIPopoverController was extremely slow to appear on occasion (sometimes it was sluggish, other time almost fine, other it was taking like 4" to appear...), and this happened only on iOs8 (iOs7 was always blink-fast), which leads me to believe that my solution might help you as well.
After much debugging I have come to the conclusion that the problem for me was connected the fact that I showed the popover within the
- (NSIndexPath *) tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath;
method (but I would'n rule out that other similar cases exist).
My solution is running the presentPopoverFromRect: method (my logs showed it was the slow method) just after the willSelectRowAtIndexPath: method ended (as a matter of fact I crammed the whole popover "initialize and show" in the function to delay as well, to be on the safe side, and because my code was already like that).
This boils down to running the UIPopoverController initialization and display code in a block like this one:
dispatch_async(dispatch_get_main_queue(), ^() {
// Do the popover stuff here
});
Even if you are not using table, other similar problems might exist within iOs8 (because this is an Apple bug!), so I guess this might worth a try...
Hope it helps,
Cheers

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

iOS development Animating Table view custom cell content

I am working on new application and i would like to have custom table view. I need to have cell that have 2 labels 1 check box and 2 buttons. The problem i have is that 2 buttons need to be shown only if user press edit button on top. I made all this to work but i have some problem with reusable cells or something like that.
I made custom cell class so i can assign data to it.
First state is: First state on img
Then when user press edit button on top i need everyting to animate and come to right place.
I want text to animate to right to make space for button, delete button needs to come animated from left side and edit butom to come in animated from right side.
This is what i want to get after animations are done: Second state on img
I made all this to work but now i have problem that first 2 cells that will come on screen after i scroll down are not effected by my code :(
This is what i get: Third state on img
i will post my project code here and if some1 can help me with it i would be more then thrilled. I point me to right way to do this.
Code downoald: https://www.mediafire.com/?g1g6d7h33mpkyt7
and i will post some code here that i think is crucial to my problem:
for animations i use:
[cell.infoView setFrame:CGRectMake(20, CGRectGetMinY(cell.infoView.frame), CGRectGetWidth(cell.infoView.frame), CGRectGetHeight(cell.infoView.frame))];
[UIView animateWithDuration:0.4
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
[cell.infoView setFrame:CGRectMake(70, CGRectGetMinY(cell.infoView.frame), CGRectGetWidth(cell.infoView.frame), CGRectGetHeight(cell.infoView.frame))];
}
completion:nil];
for cell init i use:
CICustomCell *cell;
cell = [tableView dequeueReusableCellWithIdentifier:CellTableIdentifier];
if(cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"HCICustomCell" owner:nil options:nil];
cell = [nib objectAtIndex:0];
}
cell.itemName = [dwarves objectAtIndex:indexPath.row];
cell.quantity = 25.99;
for table init i use:
UITableView *tableView = (id)[self.view viewWithTag:1];
tableView.rowHeight = 60;
UINib *nib = [UINib nibWithNibName:#"HCICustomCell"
bundle:nil];
[tableView registerNib:nib forCellReuseIdentifier:CellTableIdentifier];
img: http://i270.photobucket.com/albums/jj109/lazardj/scsimg.jpg~original
EDIT:
I also tried something now. If i start my activity then scroll down a bit so i get some new cells i dont have this problem. So it must be something with creating cells or reusability of cells or something like that. I am new to ios development so i cant figure this out on my own :( so please help :) Thanks
In your cellForRow function in your tableview delegate you need to deal with the location of your labels. So every time you request a new (or reused) cell you need to check whether you are in edit mode and then set the labels frames accordingly.
It appears as you are already doing this for the buttons, as they show up in the proper place, but the labels are being re-used at their initial locations (when they were released by the tableview).
I've had a look at your project, and the problem appears to stem from using AutoLayout. The automatic constraints appear to be interfering with your view layouts. If you go to your HCICustomCell.xib file and click on the file inspector tab, you'll see a checkbox for "Use Autolayout". Uncheck this and then set the auto-resizing constraints manually for each view in the Size Inspector tab. I've uploaded a revised version of your project here.
SOLUTION:
I done some more work on this. Tried solutions you provided me but there are still times when it gets bugged so i came with my own one. I figured out that the problem is that you cant move view in cell on first init but you can show hide them. So i decided to try to make same info view on place where it needs to be when enable is on. When enable is off its hidden and when enable is on it shows up. This is what fixed my problem :) I know its not the smartest way to do things but i managed to do what i wanted and its not messing performances of application.
This is project with fixed bugs. If any1 want can use it :)
Thanks for all your time and suggestions.
Project: https://www.mediafire.com/?uhatbh8c20xu49v

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