CATransition as an explicit animation - iphone

Greetings to all.
Does anyone know if it is possible to essentially convert CATransition to an explicit animation? That is, I almost want it to behave like CABasicAnimation does- I need to be able to create the CATransition object, set the options for it (duration, timingFunction, delegate, type, fillMode, removedOnCompletion, etc)- then essentially store it for later use and make sure it isn't trying to animate stuff that's happening between the CATransition object creation and the point in time at which I actually need it to animate a set of properties.
If I'm using CABasicAnimation, I can easily do this as nothing is animated until the animation is added to the layer and only the property specified in animationWithKeyPath is animated between toValue/fromValue. However, CATransition apparently fires [CATransaction begin] the moment you create the animation object for capturing property changes.
What I need to be able to do (as per above) is create the CATransition object, set the above options, then put it away until I actually need to use it- at which point I'll change the actual properties that it needs to animate, then add it to the appropriate CALayer via addAnimation.
Does anyone know how to do this safely?
PS: The reason for this is because my application depends heavily on a custom animation queueing system that handles animation dependencies. For example, certain things need to animate before an orientation change occurs, so I can queue ~10 animations which will all fire off AND the orientation change animation object as well- but the orientation change animation won't fire until the previous animations that it depends on have finished. Incidentally, CABasicAnimation can be wrapped up nicely into a queued animation object class. CATransition seems to be a bit of a different beast, hence the above question- I need to be able to safely create an animation queue object, setup a bunch of CATransition options, queue it up, then actually have it execute later on (meanwhile all sorts of stuff is happening) when all of it's dependencies have been satisfied (in this case, CATransition is being used for the orientation change animation).
Thanks,
-Keven Tipping

Related

CoreAnimation gets jerking

I've created an UIView and some layers. I've organized those layers into superview-subview hierarchy with the root in UIView's layer, added some gesture recognizers and I am trying to manipulate layers' geometry on events from gesture recognizers (setting bounds and position for sure). I use implicit animations and CATransactions. No explicit animations are used.
The actual result is good enough if I create CATransaction with duration = 0. But if I set the duration to 0.2 I get some strange results: as event come not rapidly and previous transaction have time to be completed before the next starts everything is OK (for example on tapping), but if there is not enough time (for example on pinch or pan) the layer being resized starts jerking. Visually it looks like animation rolls back to the initial point and starts again to the new value.
I do not see any reason for this. I've tried to perform the layout in -[UIView layoutSubviews] and invoke setNeedsDisplay in gesture recognizers handlers. I've also tried to separate this logic into separate method but it does not help.
I repeat once more that I use only implicit animations. And what I want to know is why does it happen (but not how to work around).
Any clues?
I think, it's because you start new animation when previous not finished.
Maybe, you need to stop it like
[yourView.layer removeAnimationForKey:#"yourAnimationKey"];
and you need to implement animation delegate method
-(void)stopAnimation:(id)sender
{
yourView.frame = [[yourView.layer presentationLayer] frame]; // that set frame of your view to it's animation position
}

How to reactivate an applied CABasicAnimation or check if a CABasicAnimation is actually running right now?

This is strange:
I have a view containing a rotated CALayer. The layer is rotated using a CABasicAnimation on the transform.rotation.z keypath. Works fine.
Then the view including that layer becomes deallocated, and some time later I load everything again. I re-create the view and the rotated layer, and apply that CABasicAnimation. In some situations the animation just won't start.
So what I do next is this: Performing a delayed selector to kick the animation, should it be stalled. But since kicking it while it is already running causes a noticeable hitch on the screen, I check if the animation object exists:
CABasicAnimation *anim = [rotatedLayer animationForKey:#"transform.rotation.z"];
if (!anim) {
[self startRotating];
}
Unfortunately, when the animation is not taking place, I do have a CABasicAnimation object applied. Logically, because I created it. The animation just won't start. However, when I then go ahead and apply it once again after one second or so, then it works.
I even get the animiationDidStart message from the delegate:
- (void)animationDidStart:(CAAnimation *)theAnimation {
NSLog(#"animation started!!!!");
}
There's a good chance that this is not a bug but a feature.
Is there a way to give that CABasicAnimation object a kick to make it run, even if CoreAnimation thinks that it is running already (which is not the case sometimes), without giving it a kick in the butt when it actually really was running already?
Go through the pause and resume of animation as given in Apple's Technical Q&A

Force redraw with CADisplayLink

I'm currently using CADisplayLink to show an OpenGL animation which works great. Sometimes, however a parameter changes and I need to redraw the view immediately and can't wait until the next frame is requested by CADisplayLink. If I don't do that, I get one frame wrong which looks really bad in my case.
So, how can I force a redraw of an EAGLView without interfering with the CADisplayLink stuff?
If your CADisplayLink is calling method drawFrame, for example, then just call drawFrame yourself when you need to. No reason you need to wait for CADisplayLink if you don't want to.
Your question suggests that you're storing your data in your view rather than in a data object. You should be able to change your data at any time, and your view should update when its needed to display. Move the data to a model object, and have the EAGLView draw itself based on the data when requested from the CADisplayLink rather than redrawing itself when the data changes.

UIImageView Intro Animation issue

I am trying to create an intro animation for my iOS app and am having issues with timing. In particular I would like to change screens after the intro animation plays. I currently use a UIImageView and there does not appear to be a way to do this. Many stackoverflow questions say to use an NSTimer or performSelector:afterDelay but these are not accurate timers and in my case are completely wrong. Here is what I am doing.
Set UILaunchImageFile to LaunchImage.png
AppDelegate allocs an IntroViewController
IntroViewController.LoadView allocs IntroView
IntroView.initWithFrame performs the following
UIImageView* iv =
iv.animationImages =
iv.animationDuration = 2.0
iv.animationRepeatCount = 1
[iv startAnimating]
Set NSTimer/performSelector:afterDelay?
When timer triggers change from IntroViewController to something else.
If I perform either step 5 or 6 it does not work correctly. It does correctly play the animation and it will correctly change the view/view controller, but the timing is horribly horribly wrong. When you call startAnimating in this manner it may not actually start the animation for a full second or two. I presume because the app is still loading in resources somehow. This time however is not consistent across the simulator or all devices. Infact several runs on the same device may have different results. Thus I can not hard code some delay.
All I want to do is detect that a UIImageView animation has played the last frame and do something. That's it. The best solution I've found so far is to set a timer in some manner and then do something, but in my situation a timer is not a solution.
Any ideas?
The long delay you observe is due to reading and decoding the images, which UIImageView does before the animation begins.
Core Animation performs the animation for you, and it does its drawing in the render server, which is in a separate process. Remember that what you see on the screen doesn't necessarily represent your app's instantaneous picture of your layer tree: Core Animation Rendering Architecture.
UIImageView doesn't provide facilities to give you accurate results here. I'd suggest:
Make a UIView of your own.
Create a CAKeyframeAnimation with discrete calculation mode and your images' CGImageRefs as its values.
Set the animation's delegate to your IntroViewController.
Add the animation to your view's layer for the "contents" key.
Your IntroViewController will get animationDidStop:finished: when it's done.
Two things to consider, though:
First, you'll get better results using a movie rather than a series of images, since the movie can be streamed from storage.
Second, and more importantly, this solution will improve the timing situation but will not totally mitigate it. animationDidStop:finished: is called when your app thinks the animation is doneā€¦ which is not necessarily exactly when it appears to finish.
You'll do better if you don't rely on delegate callbacks for media timing: instead, add this animation and the animation transitioning your views (using a CAAnimationGroup if necessary) in the same turn of the run loop. Delay the latter with a beginTime of the first animation's duration. Depending on what you're doing, you may have to set the second animation's fill mode as well to get the correct behavior during the first.

Observing animated property changes in a CALayer

I have a CABasicAnimation that animating a property of a CALayer e.g. bounds.origin. I want to be able to observe the property changing over time, but haven't really found a method that works 100%.
I tried using KVO (key-value observation) on the presentationLayer's bounds.origin keypath. The system complains that the object is freed before its observers are freed, leading me to think that the presentation layer is only temporary. Observing presentationLayer.bounds.origin as a keypath doesn't work.
I tried creating a property on another layer and animating that e.g. by declaring the #property and making it #dynamic on that layer. However this new property only gets changed when the presentation layer is accessed (e.g. at the end of the animation), it doesn't seem to update while the animation is running.
I used needsDisplayForKey on the property in #2, which does trigger updates during the animation, but for these issues:
it only works if the CALayer has non-zero frame. Since this layer might be a CAShapeLayer or subclass, it may have a zero frame.
it looks like it triggers setNeedsDisplay for that layer, but since I'm not actually drawing that layer only monitoring the property change, I don't want to cause it to redraw.
I tried scheduling an NSTimer, and within the timer callback sample the presentationLayer. This also works but for these issues:
The timer would probably be slightly out of sync with the animation update.
Since occasionally the original animation gets pre-empted by another animation, it's difficult to actually get the timer to fire when the animation is running and only when the animation is running.
Any suggestions? All this would be on iPhoneOS 3.0/3.1.
Try using CADisplayLink, which is designed to stay in sync with the animation loop. More info: https://ashfurrow.com/blog/animating-views-with-cadisplaylink/
I think you've named all of the possibilities. In fact, I wasn't even aware of #2 and #3 and I wrote the book on Core Animation. ;-)
KVO is not available for these properties. Would be nice if it were, but I believe the reason for this has to do with the overhead it would take. The value would update very frequently and have to call back to any observers.
Anyhow, I've found the NSTimer to be the most reliable approach, but now I'm not sure from what you've said. What makes you think that the timer is out of sync? Why is it difficult get the timer only to fire when the animation is running? Can't you just check for the condition you want in the timer callback and then do nothing if the condition is not met?
Best Regards.
The best solution for me is using both: CABasicAnimation and CADisplayLink together. You can start observing changes on start animation and finish on it's finish. You also can calculate each step, but pay attention of timing logic. It will be working when you use linear timing. Or you have to prepare similar logic on your class based on CADisplayLink (transformation aligns to time changes).