CALayer animation jumpy in UITableViewCell on iPhone 3gs 4.2.1 - iphone

I've created a custom view that resizes CALayers in when a pan gesture is triggered. When the control is added to a normal UIVIew, the animation behaves exactly as expected.
When the view is added to a UITableViewCell via addSubViewthe animation is jumpy. I placed some NSLog statements in the gesture handler, and it's behaving exactly as expected.
Is there anything about UITableView or UITableViewCell that would cause this unexpected behavior I am experiencing? Is it even correct to add custom view by using addSubView?
EDIT
I have a little more information on the problem. The issue only seems to be appearing on certain environments.
*iPhone 3GS, iOS 4.2.1 - YES
iPod Touch (Retnia), 5.1 - NO
iPod Simulator 4.2 - NO
iPod Simulator 5.1 - NO
I've also put into place the suggestions from Lorean and DavidH, which didn't solve the issue.
*When I build from XCode, there is a message next to the 3gs device that says "Overriding base SDK to 4.3"
Could this be a potential problem?

You should only add custom views to the cell's content view.
http://developer.apple.com/library/ios/#documentation/uikit/reference/UITableViewCell_Class/Reference/Reference.html

While I agree the view should be placed as a subview in the contentView, there is another potential issue. Animations inside moving scrollViews are historically terrible, as the overall system is not designed to support two uncoupled animations.
The solution for that issue is when a user touches the cell, you disable scrolling (ie myTableView.scrollEnabled = NO, then when the pan gesture completes (all animations), enable it again.
EDIT: This is going to sound a bit funky, but should work as I've done similar in the past. When you get the first touch event, calculate what the frame of the layer is self.view coordinates, and then add it to the view using this frame, but saving the old frame. User sees nothing and now your view is on tip. Obviously you must also prevent the table from moving. When you are done with user interactions, then move the view back to the cell using its old frame.
Depending on how you are doing touches, you may need to intercept the first touch in a clear UIView that overlays the desired panning view, and when it gets touched, do all the above, then forward the initial touch to the view. Hopefully this step is not needed.

I've implemented a hack which solves the problem. I may decide to turn off the animation altogether as it's not really needed, but if anyone has a better answer than this I will gladly accept.
if ([[[UIDevice currentDevice] systemVersion] floatValue]<5.0)
{
[CATransaction begin];
[CATransaction setDisableActions: YES];
}
//do stuff
if ([[[UIDevice currentDevice] systemVersion] floatValue]<5.0) [CATransaction commit];

Related

setStatusBarOrientation causing problems in iOS8

I'm having real problems with my apps under iOS8. I've made a bunch of OpenGL based apps, which don't need the iOS to handle the orientation. It's all handled within the OpenGL section. But I do use setStatusBarOrientation to make sure any dialogs/keyboards etc that pop up line up with everything else. I'm creating my window and view programatically or in a xib, I've never needed to use a storyboard (in case thats relevant?)
It's all been perfect, til iOS 8 came along.
Now it seems that setStatusBarOrientation isn't behaving properly at all. It's either not changing anything, or it's rotating "something" that stops touches being recorded on half the screen. It's as if the window is being rotated within itself, but visually nothing changes, just the touches are effected.
It's hard to explain, and makes no sense.
But my question is: How do you set the status bar orientation in iOS8? And how do you do it without destroying everything else that works in previous iOS versions?
Set iOS7.1 as your Base SDK, and it'll work fine on iOS8, without all these strange bugs.
http://objcsharp.wordpress.com/2014/09/21/how-to-get-back-the-ios-7-sdk-after-upgrading-to-xcode-6/
found this
if ([UIViewController class]) // iOS8 has this class only
{
[[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIDeviceOrientationPortrait] forKey:#"orientation"];
}
found here:
http://blog.lessfun.com/blog/2014/09/24/ios8-issue-keyboard-orientation-and-idletimerdisabled-not-working/
seems to work

Assertion failure in UIQueuingScrollView didScrollWithAnimation:force:

I've got a UIPageViewController set up paging my ImageViewController.
The ImageViewController contains a UIScrollView with a UIImageView inside. Nothing else.
I'm testing at the moment with 3 "items" in my datasource for the UIPageViewController (i.e. three pages).
It all works fine and I can scroll and zoom and then page for about 30 seconds and then suddenly I get this warning...
*** Assertion failure in -[_UIQueuingScrollView _didScrollWithAnimation:force:], /SourceCache/UIKit/UIKit-2372/_UIQueuingScrollView.m:778
I've got no idea where to start debugging it though as it doesn't point to any of my code and there isn't any of my code in the stack or anything.
Can someone give me a pointer as to where to start debugging this.
EDIT
I've done a bit more testing. It seems to happen if the scrollView is decelerating (i.e. after a flick) and then I try to transition the PageViewController to another ViewController as the scrollview is still moving.
The app crashes about 20% of the way through the transition to the next page.
EDIT 2
The error seems to stop on the line _cache_getImp (not sure if that's a capital i or lowercase L).
EDIT 3
This gets better. I just downloaded Apple's PhotoScroller sample app to see if they got round the problem a different way. Well, no, they didn't. The sample app crashes in exactly the same way mine does! You have to zoom and scroll and transition pages at the same time to make it more likely to crash but it happens on it's own too it just might take longer to happen.
Came up with a solution! In my case I have a UIPageViewController with UIPageViewControllerTransitionStyleScroll as well as next buttons that allow the user to advance through my viewpager by tapping. I am getting this crash when the user presses a next button and drags around slightly before releasing their finger (still within the frame of the button). It appears that the dragging within the button is interfering with UIPageViewController's pan gesture recognizer in this case, and that's what causes the crash.
While it's pretty unlikely that the user will get in this state, I've come up with a relatively simple solution the prevents my app from crashing if it does happen. I have a boolean that represents if the user is in a valid state to move to the next screen and set it to YES on touch down, then to NO if the user drags anywhere inside my button. Then, on touchUp (nextPressed) I check the boolean before moving my UIPageViewController programatically.
- (IBAction)touchDown:(id)sender
{
self.shouldAdvanceToNextScreen = YES;
}
- (IBAction)touchDragInside:(id)sender
{
self.shouldAdvanceToNextScreen = NO;
}
- (IBAction)nextPressed:(id)sender
{
if (self.shouldAdvanceToNextScreen) {
UIViewController *initialViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"TutorialScreen2"];
NSArray *viewControllers = [NSArray arrayWithObject:initialViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
}
}
The downside is that nothing will happen even though the user still released their finger within the button frame. However, I prefer this over a crash and see this as a pretty rare edge case regardless. I'd expect the user would just tap again - this time without a tap & drag - and move forward successfully.
I'd welcome any ideas on taking this a step further and preventing the clash between the touch drag and the UIPageViewController altogether.
Have you tried disabling bouncing in the UIScrollView? That worked for me and solved my other problem too alluded to in my comment above.
I have been fighting with this all day.
My Conclusions:
If you have a scrollview as the showing ViewController and you are delegate of the scrolls: you're in trouble. Even with the PageViewController configured with Horizontal scrolling, a vertical scrolling on your view will trigger an event. -> this does not cause trouble if: you scroll back to the top of your view before (Not sure how to fix this).
There are good StackOverflow threads like this one.
Basically the solution is:
1 [yourView setViewControllers:yourControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
2 Use UIPageViewControllerTransitionStylePageCurl as the transition Style.
This fixed most of my problems.
If someone has a solution for the delegation problems with the scrolling, will be most wellcome
I had a similar problem. My setup was a UIPageViewController and I was loading view controllers with an UIImageView inside. When interacting while the UIPageViewController was scrolling I got the same crash log.
I fixed it by creating a UILongPressGestureRecognizer and add to the UIPageViewController's scroll view.
Created my own subclass of UIPageViewController (Solution is specific for my case but can easily used as a generic solution)
Find the inner UIScrollView of the UIPageViewController
- (UIScrollView *)findScrollView
{
UIScrollView *scrollView;
for (id subview in self.view.subviews)
{
if ([subview isKindOfClass:UIScrollView.class])
{
scrollView = subview;
break;
}
}
return scrollView;
}
Add the long tap gesture recognizer to the inner scroll view and point its action to a method or nil
/**
* On tap-hold the page view controller crashes as soon as it pages to a new view controller.
* Setting a long press gesture to ignore the hold.
*/
- (void)listenForLongPressGestureOnScrollView:(UIScrollView *)scrollView
{
UILongPressGestureRecognizer *longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:nil];
[longPressGestureRecognizer setMinimumPressDuration:0.5];
[scrollView addGestureRecognizer:longPressGestureRecognizer];
}
It surely looks like a bug in iOS 8 and 9.
(and the "answers" down below attempt to work around that)
Feel free to file a ticket on bugreport.apple.com
Do be sure to specify how to crash PhotoScroller
Include all iOS versions you've seen it crash on
To date I've seen:
Assertion failure in -[XXX.TrackingPageViewController queuingScrollView:didEndManualScroll:toRevealView:direction:animated:didFinish:didComplete:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3512.60.12/UIPageViewController.m:2028
Assertion failure in -[_UIQueuingScrollView _didScrollWithAnimation:force:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3512.60.12/_UIQueuingScrollView.m:785
(lldb)
I'll keep the list updated (Despite the downvoting, however massive).
Stay tuned.
It does not look like our bug.
UPD 2016 09 27 (on Xcode 8 now):
Assertion failure in -[XXX.TrackingPageViewController queuingScrollView:didEndManualScroll:toRevealView:direction:animated:didFinish:didComplete:], /SourceCache/UIKit/UIKit-3347.44.2.2/UIPageViewController.m:1875
awesome

Are animations automatically removed from layers when switching viewcontrollers?

I've setup a simple CAKeyframeAnimation with infinite repeatcount and added it to a layer. All is fine except that the animation is removed when I switch to another tab (or very likely any other mechanism of changing the active view controller).
Is this documented anywhere, perhaps new in iOS 5, and is there another solution than starting the animation again in viewWillAppear? The timebase is irrelevant in this case, starting the animation from time 0 would be fine.
You should not perform any work with View while it is not on screen, because it's just waste of iPhone resources. Besides your application can get memory warning and view will be unloaded.
IMHO the best approach is to set up graphics in -(void) viewWillAppear and start animations in -(void) viewDidAppear.
P.S. In -(void) viewDidLoad parameters of frame of view are still not valid and if you try to use them, you can get something wrong.

Possible to cancel an autorotation once a shake gesture is recognized?

Does anyone know if it is possible to stop an autorotation once a shake gesture is recognized (i.e. don't autorotate during an aggressive shake)?
Autorotation is handled with some quirky low-level stuff which seems to change significantly betweeen major OS releases; I really wouldn't mess with it.
"Cancelling" doesn't really work either — in CoreAnimation speak, the animation has already happened in the "model tree" and the "presentation tree" and "render tree" are catching up. If you've saved the old orientation (i.e. on a rotation, save the old orientation and the time, and compare times) you can call [[UIDevice currentDevice] setOrientation:orientation] but Apple will reject your app.
If the rotation hasn't happened yet, then set a flag at the start of the shake and clear it at the end, and then override -shouldAutorotateForInterfaceOrientation: to return NO if a shake is currently happening.
Register your controller to check if a shake occurs by observing the device orientation, and set a flag in your controller for knowing that shake is currently happening (you could use the date). Then, override shouldAutorotateForInterfaceOrientation method to check the flag and avoid rotation in case the shake is happening now.

Single-Stage vs Two-Stage Animation for iPhone Apps?

What are single-state and two-stage animation for rotating an iPhone window?
This is the "error" message I get in the Debugger Console (nothing crashes):
Using two-stage rotation animation. To use the smoother single-stage animation, this application must remove two-stage method implementations.
I was working through the book "Beginning iPhone Development: Exploring the iPhone SDK" by Apress (Dave Mark, Jeff LaMarche) on the Swap Project.
Everything is explained in the UIViewController Class Reference. Especially check out the View Rotation section near the top.
From the reference:
Handling View Rotations
By default, the UIViewController class
displays views in portrait mode only.
To support additional orientations,
you must override the
shouldAutorotateToInterfaceOrientation:
method and return YES for any
orientations your subclass supports.
If the autoresizing properties of your
views are configured correctly, that
may be all you have to do. However,
the UIViewController class provides
additional hooks for you to implement
additional behaviors as needed.
To temporarily turn off features that
are not needed or might otherwise
cause problems during the orientation
change, you can override the
willRotateToInterfaceOrientation:duration:
method and perform the needed actions
there. You can then override the
didRotateFromInterfaceOrientation:
method and use it to reenable those
features once the orientation change
is complete.
If you want to perform custom
animations during an orientation
change, you can do so in one of two
ways. Orientation changes used to
occur in two steps, with notifications
occurring at the beginning, middle,
and end points of the rotation.
However, in iPhone OS 3.0, support was
added for performing orientation
changes in one step. Using a one-step
orientation change tends to be faster
than the older two-step process and is
generally recommended for any new
code.
To add animations for a one-step
orientation change, override the
willAnimateRotationToInterfaceOrientation:duration:
method and perform your animations
there. To use the older two-step
method, override one or both of the
willAnimateFirstHalfOfRotationToInterfaceOrientation:duration:
and
willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:
methods to configure your animations
before each step. You must choose only
one technique and override just the
methods associated with that
technique. If you override either
method associated with the two-step
technique, the view controller uses
that technique by default.
I have found the culprit in my case to be the UIImagePickerController (I also do not override any rotation animation):
[self presentModalViewController:imagePicker animated:YES];
Replacing imagePicker with a generic UIViewController doesn't generate any warnings.
I changed from willAnimateFirstHalfOfRotationToInterfaceOrientation:duration: method to willAnimateRotationToInterfaceOrientation:duration: method and warning gone.
Thanks.
Ed Marty's answer is the correct one. The reason it will happen if you are not overriding any of the rotation animation is probably that you reply "YES" to shouldAutorotate.. for some view. If you do not implement rotation at all, then you should just not override the shouldAutorotate.. method. If you do override that method, then just override the single step rotation method as well and pass it along to the super.
If you're using iOS 4 and you're getting this warning, I found a way to get rid of it. In your info.plist, there is an item called "Supported interface orientations." Select which orientations your application supports and two-stage warnings will go away when bringing up the imagePicker.
#plumiscles answer didn't quite work for me - there was no item called 'Supported Interface Orientations', probably b/c it is an old project. But you can get the same effect by editing the .plist file directly and adding this:
<key>UIInterfaceOrientation</key>
<string>UIInterfaceOrientationPortrait</string>
Need to add UIImagePickerController as a subview to solve this error
[self.view addSubview:picker.view];
[self presentModalViewController:picker animated:NO];
I've had this issue after creating a tabbarcontroller with no view controllers inside (no tabs), this warning disappeared once I attached at least one view controller to it.
I wasn't over riding any of those two-step functions, but I was calling my own function when I received orientationChanged notifications, and I had this line of code in it. Commenting it out got rid of the warning and allowed the auto rotate to work properly. Auto rotate still worked with this line of code until iOS 4.2, then it broke completely. Spent a lot of time looking for why the built in autoRotate stopped working in 4.2. Maybe this will help someone else.
Commented out this line to make it work:
[[UIApplication sharedApplication] setStatusBarOrientation:currentOrientation animated:YES];
I've delete from plist "Supported interface orientations" row and warning disappears.
I just had the same problem. In my case was a silly mistake that I'm putting here just in case anyone else falls into that same issue.
In my tabbed app I remove one of the original ViewControllers and added a new one with Storyboard to create a "Settings" section.
This new VC had to be a table view VC and even I designed, compiled and run it without a problem, when I changed the orientation of the app I kept getting this “Using two-stage rotation animation” error.
My problem was that I forgot to change in the original .h file interface "UIViewController" for "UITableViewController".
Once this was done I changed on the Storyboard identity badge the class from the general value to my SettingsViewController and that was the end of it.
I hope it can help someone else. It took me a while to get to bottom of this.
Cheers,