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).
Related
I have a button press that fires off an animation, and upon completion of the animation, changes the text of a label. I'd like to write a test verifies that when the button gets pressed, eventually the text of the label changes properly.
The implementation of the button press IBAction will use [UIView animateWithDuration:animations:completion:]. I obviously don't want my unit test to actually wait 0.5 seconds for an animation to complete.
I thought about mocking UIView, but it seems odd to inject UIView as a dependency to a view controller. Further, the mocking framework I'm using (OCMockito) doesn't seem to work well with mocking class methods.
I also thought about method swizzling or writing a testing category for UIView, and using an implementation that does nothing but invoke the animations: block followed by the completion: block. That seems a bit broken to me; I worry that overriding the implementation of a class method on UIView may have unintended consequences down the road.
Being new to TDD, I'm not sure what best practice is here. Is this one of those pieces of code that should be considered "UI twiddling" and therefore it's acceptable to leave untested? Or is there some more obvious way to test this that I am missing?
I would simply make a property that determines the length of the animation and have it default to 0.5 seconds.
That way, your test can set the animation duration to 0 and observe that the label's text is updated without waiting.
This is dependency injection, and it's wildly useful if you just starting with TDD. It also has the nice side-effect of make your code more modular and less coupled.
I have a simple UIView that draws itself using drawRect:. In order for the view to animate, the drawRect method needs to be called every say 0.05 seconds, so I use a repeating timer:
timer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self
selector:#selector(setNeedsDisplay) userInfo:nil repeats:YES];
I don't know too much about run loops, and threads, and all that system stuff, so I want to know if this is the correct way to run an animation? This timer repeats itself endlessly. Is this something I should be worried about? Will this block the main thread? What can I do to minimize overall impact on performance?
The approach is not bad, but there are other ways to do it.
The timer's target method must take the timer as an argument, so instead of setting it for setNeedsDisplay directly, you should set up a method like this:
- (void)animationTimerDidFire:(NSTimer *)timer
{
[myView setNeedsDisplay];
}
If your view will always be visible, then you can just set-and-forget the timer. On the other hand, if it may go away because you switch to a different view, you will need to invalidate and recreate the timer as needed.
The main thread of your app uses a run loop and calls out to various methods in response to events, like user taps, system notifications (e.g., memory warning), and I/O arriving. If anything the run loop calls takes a long time to return, it will hold up everything in the queue. When you set up a timer, it is added to a list and that the run loop checks it each time through; if one of the timers is ready, it calls your method.
The end result is that timers are not exact: they might not fire as often as you like, might be called late, etc. Again, if your app is pretty simple, the main run loop won't be very busy and so a timer will probably be good enough. Just make sure your animation is based on actual time elapsed between calls, rather than assuming each call happens exactly 0.05 seconds apart.
Alternatives
If your animation simply involves flipping through some static images, UIImageView has some support for this.
If creating each frame of animation takes a noticeable amount of time (and you don't want to block the main thread), you could use a background queue to draw into an image (see CGBitmapContextCreate and CGBitmapContextCreateImage), then signal the main thread when a new image is ready to display. Anything that touches a view MUST happen on the main thread, but you can do the drawing on the background.
You also might want to read up on CALayer in the QuartzCore framework. This is what the UIView objects actually manipulate to draw on the screen. You may find that, instead of drawing, you can get the effects you want by manipulating some CALayer objects and letting Core Animation do the heavy lifting (e.g., you change the color from red to blue, Core Animation takes care of fading from one to the other).
Well, if you are using an overridden or custom method, you should use recursion and a completion block for calling the animation. I find it works much better than a timer, since timers aren't always exact and can cause some animation issues if you have the animations timed for cycling.
EDIT: I don't know much about using drawRect: and calling [self setNeedsDisplay] to update it, so I can't help you in that regard. Sorry.
I'm wondering if iOS allows one to do the following:
I have a puzzle game and I've been working on saving data when the user returns to the home screen. To do this, using NSNotificationCenter, I had one of my game classes to observe [UIApplication sharedApplication]'s ApplicationWillResignActive method so when that happens I save my game state. But if the user decides to exit while animations are going on, the game will save a midState where the model values are still changing and that will often cause crashes. My question is if it is possible to somehow delay the saving process (even though it is on the background) until the animations are complete (or some variable equals 1)?
My first idea is to create scheduled event with NSTimer to try to save until everything is set. Any ideas would be appreciated. Thank you
You can use performSelector:withObject:afterDelay:
// Call doSomething after a 1 second delay
[self performSelector:#selector(doSomething) withObject:nil afterDelay:1.0f];
Rather than trying to delay the saving, especially in ApplicationWillResignActive, you should look into how to stop the animation and record the expected final values of the animation. Depending on the method of animation (UIView static methods, block based, 3rd party) there is usually a way to stop them, and since you define what the animation does you should already know the final state.
In the case of block based animations:
How to cancel UIViews block-based animation?
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.
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.