How to save data with a delay? - iphone

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?

Related

Running an endlessly looping animation on iOS

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.

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.

How to respond to any kind of interruption?

My app is playing a pretty complex animation. It's like a flipbook.
What I do is: I have a huge loop with selectors, and after every delayed call the next one is called.
Now someone calls the user and the device suddenly shows up this fat green status bar and maybe some big pick-up-the-phone-call overlay. Or: The alarm clock rings, and a big alert sheet appears in front of just about everything.
It would be great to just pause the whole animation in case of ANY interruption. Probably I've also missed like 5 more possible interruptions.
How are you doing that? How do you get notified for all interruptions and then call one single -stopEverything method?
Whenever the app becomes inactive, the UIApplicationWillResignActiveNotification local notification will be posted. In the opposite suituation, the UIApplicationDidBecomeActiveNotification notification will be posted.
Your animation logic can listen to this and respond appropriately. There is an +setAnimationsEnabled: method to kill all current and future animations, but there isn't a documented "global pause" method.
Depending on the animation, you might be better off using CoreAnimation directly by using a single CAKeyframeAnimation on the view's -layer rather than having one animation's completion selector start another animation.

How can I accurately time animations on an iPhone inside the normal flow of a program?

I've been using CALayer animations in my program, primarily to move around CALayers with animation on my screen. My problem is that I don't want to exit the layer-moving (card dealing, as it happens) method until all of the cards are done dealing. How can this be done cleanly?
Presume that my layer-moving method is called by different methods at different times, so it's better to let the normal flow of the program bring me back to the starting place, rather than trying to go back and forth with delegation.
Trying something simple like udelay() didn't work because it appears to block the display of the animation too.
Any ideas? Bonus if it's something I could use to pause for the completion of a built animation too (specifically, the animated dismissal of a modalViewController in what I'm working on now, but it'd be nice to have mechanisms that would regular pause, then continue on when various Animated: things occur).
CAAnimation has a delegate method animationDidStop:finished: and UIView has setAnimationDidStopSelector:. You can pause what you are doing at the beginning of the animation, and use these to resume. For example, if you want to pause a timer, set timerRunning = NO right at the animation code, then timerRunning = YES in the didStop method.

Timer and animation events trumping TouchesEnded events

I've implemented a tap-and-hold handler using an NSTimer that I first set in the TouchesBegan overload.
However, what I actually want is for an action to be continuously performed in quick-fire succession while the touch is being held. So, on timer expiry I call a handler to do the work, which then sets another timer and the cycle continues until the TouchesEnded comes in and cancels it, or another terminating condition is met.
This works fine, until my handler code triggers an animation to go off at the same time.
Now we have animation events and timer events going off, and in all that we need to handle TouchesEnded as well.
What I am finding is that, if the animation is triggered, and I set my timer to less than 0.025 seconds, my TouchesEnded event doesn't come through until the timer cycle stops (the other terminating condition). Setting a slower timer, or not triggering the animation, make it work (TouchedEnded comes in straight away), but are not what I want.
Obviously this is all on the device (release build - no NSLogs) - in the sim it all works fine
Is there any way of setting the relative priorty of these events - or is it likely I'm missing something else obvious here?
[Update]
I've worked around this in this instance by doing the continuous part without visual feedback until it's done (which from this users perspective is instant). I think this is ok for now. I'd still like to hear any more thoughts on this (Jeffrey's idea was good), but I'm not waiting on tenterhooks now.
Try writing your own Timer-type class by spawning off onto a thread. Example:
BOOL continue = YES; //outside of your #implementation
-(void)doLoop
{
while(continue){
[NSThread sleepForTimeInterval:.025];
[self performSelectorOnMainThread:#selector(whateverTheFunctionIs) waitUntilDone:YES];
}
}
and this would be started by [NSThread detatchNewThreadSelector:#selector(doLoop) toTarget:self withObject:nil]. This is not exactly threadsafe, but you can choose to wrap the boolean into a NSNumber and then do #synchronize on it if you so choose. Alternatively, after I wrote that little snippet I realized it would be better to do a check against the current NSTime instead of sleepForTimeInterval: but you get the point. :)