Pause and resume a performSelector afterDelay? - ios

What I want to do is call a method after a delay for a game which will reload a weapon, but if I do that and the user pauses the game that selector isn't getting paused so the user can cheat by pausing.
So Im wondering if there is a way to pause it and then continue.
I know how to cancel the selector with this: cancelPreviousPerformRequestsWithTarget: but I want it to be able to continue after the user resumes the game.
Is this the right approach or should I consider another strategy?

This is absolutely not a good way to solve this problem, but it did the trick for me.
I've got a scaling animation, where a view grows to scale 6.0, waits for 2 seconds and then scales back to 1.0.
The wait for 2 seconds is my part where I used the performSelector:withObject:afterDelay: which I couldn't pause.
This is the pause part:
[UIView animateWithDuration:0 delay:2.0 options:0 animations:^{
self.transform = CGAffineTransformMakeScale(6.0 + 0.001, 6.0 + 0.001);
} completion:^(BOOL finished) {
[self animateOutWithCompletion:completion];
}];
Make sure you are animation a change, thats why I add 0.001 (which you wont see, as it's a that little change).
UIView animations can be paused using this category:
#interface UIView (AnimationsHandler)
- (void)pauseAnimations;
- (void)resumeAnimations;
#end
#implementation UIView (AnimationsHandler)
- (void)pauseAnimations
{
CFTimeInterval paused_time = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil];
self.layer.speed = 0.0;
self.layer.timeOffset = paused_time;
}
- (void)resumeAnimations
{
CFTimeInterval paused_time = [self.layer timeOffset];
self.layer.speed = 1.0f;
self.layer.timeOffset = 0.0f;
self.layer.beginTime = 0.0f;
CFTimeInterval time_since_pause = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil] - paused_time;
self.layer.beginTime = time_since_pause;
}
Feel free to use this code, but as I said before, this is not a good way to solve this problem. It's just making use of the way UIView animations work and the possibility to pause these animations.

Related

Pause and resume animations except of rotation in iOS?

If I need simply to pause/resume rotation then I use the following code:
- (void)pauseAnimations
{
CFTimeInterval paused_time = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil];
self.layer.speed = 0.0;
self.layer.timeOffset = paused_time;
}
- (void)resumeAnimations
{
CFTimeInterval paused_time = [self.layer timeOffset];
self.layer.speed = 1.0f;
self.layer.timeOffset = 0.0f;
self.layer.beginTime = 0.0f;
CFTimeInterval time_since_pause = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil] - paused_time;
self.layer.beginTime = time_since_pause;
}
But I want to stop the current layer animation before rotation and resume after it. And this code stops the rotation process too.
How to solve this issue?
You can't pause a layer's animation's all but one by setting the layer's speed to 0.
You might be able to fetch the active animations from the layer and pause the individual animations using the technique you are using to pause animations on the whole layer (setting the speed to 0).
Note that you can't save a pointer to the animation objects that you create because the system actually adds a copy of the animation to the layer, not the original animation. You would probably need to fetch the animation using the CALayer animationForKey method.
Disclaimer: I have never tried this. My answer is based on general knowledge of how Core Animation works.

Core Graphics animation is not smooth

So i am using Core Graphics to rotate a view
-(void)viewDidLoad
{
[super viewDidLoad];
timer = 0.0f;
// Do any additional setup after loading the view, typically from a nib.
[NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(callEverySecond) userInfo:nil repeats:YES];
}
-(void)callEverySecond
{
if (timer>360) {
timer=0.0f;
[self rotateHand:timer];
}
else
{
timer++;
[self rotateHand:timer];
}
}
-(void)rotateHand:(CGFloat)angle
{
[UIView animateWithDuration:0.01 animations:^{
CGAffineTransform matrix = CGAffineTransformMakeRotation(angle*M_PI/180);
[[self.rotate layer]setAffineTransform:matrix];
}];
[[self.rotate layer]needsDisplay];
}
Now the problem is that the rotation speed is non uniform and slows downs and speeds up randomly.
What is wrong here.
A timer with interval 0.01 wants to fire 100 times per second. iOS only updates the screen 60 times per second.
Instead of using an NSTimer to “manually” perform animation, use a CADisplayLink. A display link fires once per screen update (by default) and fires in sync with screen updates.
Also, the interval between screen updates is 1/60 = 0.01666667 seconds. Therefore your UIView animation duration is less than the time between screen updates, so it can't produce any visible animation. Get rid of your UIView animation.
If you just want to make a layer rotate continuously, you don't have to use an NSTimer or a CADisplayLink. Instead, use a CABasicAnimation on the transform.rotation property:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
animation.repeatCount = HUGE_VALF;
animation.duration = 3.6;
animation.fromValue = #0.0;
animation.toValue = #(2 * M_PI);
[self.rotate.layer addAnimation:animation forKey:animation.keyPath];
NSTimer is not meant to have very good precision. Besides, calling it 100 times/second is overkill for animation purposes. If you want a smooth rotation with a turn every second, use a repeating CAAnimation instead.

How to create a continuous animation in iOS which will also run when app in background?

I am creating a game in which i have to move coveyner belt continuously from top to bottom.The problem arises when my app goes into background and is resumed later.
The object is removed from current position, and the animation stops.
As the comments are describing you basically can't do it, at least not in an strait-forward way.
Let me show you how I would approach the following problem:
The main problem that you have is that when an object is being animated the actual position of the object doesn't really change. What changes is the presentation layer instead!
So, how can you know where the objects are exactly? Well, theoretically you can do so the by asking the layers presentation layer for the actual frame, anchorpoint, or whatever property it is that you are animating:
[[myView.layer presentationLayer] frame];
So, what I would do is when the application goes to background I would set update all the views to reflect the presentation layer. When you come back, I would resume the animations from there. The objects should be in the right place.
NSAarray *viewsBeingAnimated;
- (void)applicationWillEnterForeground:(UIApplication *)application{
for (UIView *oneView in viewBeingAnimated){
// Update whatever other properties that you were animating
[oneView setFrame:[[myView.layer presentationLayer] frame]];
[oneView layer] removeAllAnimations];
}
}
Now, once the app has resumed, you should be able to continue from there.
Hope that helps.
-(void)pauseLayer:(CALayer*)layer{
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;}
#pragma mark -resume game
-(void)resumeLayer:(CALayer*)layer{
NSLog(#"paused:%f",[layer timeOffset]);
CFTimeInterval pausedTime = [layer timeOffset];
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;}
I am using this code and i'm using block based animation to animate thousands of image-one at time falling from top to bottom of iPhone screen

How to stop and reverse a UIView animation?

I have animated a UIView so that it shrinks when the user touches a toggle button and it expands back to its original size when the user touches the button again. So far everything works just fine. The problem is that the animation takes some time - e.g. 3 seconds. During that time I still want the user to be able to interact with the interface. So when the user touches the button again while the animation is still in progress the animation is supposed to stop right where it is and reverse.
In the Apple Q&As I have found a way to pause all animations immediately:
https://developer.apple.com/library/ios/#qa/qa2009/qa1673.html
But I do not see a way to reverse the animation from here (and omit the rest of the initial animation). How do I accomplish this?
- (IBAction)toggleMeter:(id)sender {
if (self.myView.hidden) {
self.myView.hidden = NO;
[UIView animateWithDuration:3 animations:^{
self.myView.transform = expandMatrix;
} completion:nil];
} else {
[UIView animateWithDuration:3 animations:^{
self.myView.transform = shrinkMatrix;
} completion:^(BOOL finished) {
self.myView.hidden = YES;
}];
}
}
In addition to the below (in which we grab the current state from the presentation layer, stop the animation, reset the current state from the saved presentation layer, and initiate the new animation), there is a much easier solution.
If doing block-based animations, if you want to stop an animation and launch a new animation in iOS versions prior to 8.0, you can simply use the UIViewAnimationOptionBeginFromCurrentState option. (Effective in iOS 8, the default behavior is to not only start from the current state, but to do so in a manner that reflects both the current location as well as the current velocity, rendering it largely unnecessary to worry about this issue at all. See WWDC 2014 video Building Interruptible and Responsive Interactions for more information.)
[UIView animateWithDuration:3.0
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction
animations:^{
// specify the new `frame`, `transform`, etc. here
}
completion:NULL];
You can achieve this by stopping the current animation and starting the new animation from where the current one left off. You can do this with Quartz 2D:
Add QuartzCore.framework to your project if you haven't already. (In contemporary versions of Xcode, it is often unnecessary to explicitly do this as it is automatically linked to the project.)
Import the necessary header if you haven't already (again, not needed in contemporary versions of Xcode):
#import <QuartzCore/QuartzCore.h>
Have your code stop the existing animation:
[self.subview.layer removeAllAnimations];
Get a reference to the current presentation layer (i.e. the state of the view as it is precisely at this moment):
CALayer *currentLayer = self.subview.layer.presentationLayer;
Reset the transform (or frame or whatever) according to the current value in the presentationLayer:
self.subview.layer.transform = currentLayer.transform;
Now animate from that transform (or frame or whatever) to the new value:
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.layer.transform = newTransform;
}
completion:NULL];
Putting that all together, here is a routine that toggles my transform scale from 2.0x to identify and back:
- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
CALayer *currentLayer = self.subview.layer.presentationLayer;
[self.subview.layer removeAllAnimations];
self.subview.layer.transform = currentLayer.transform;
CATransform3D newTransform;
self.large = !self.large;
if (self.large)
newTransform = CATransform3DMakeScale(2.0, 2.0, 1.0);
else
newTransform = CATransform3DIdentity;
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.layer.transform = newTransform;
}
completion:NULL];
}
Or if you wanted to toggle frame sizes from 100x100 to 200x200 and back:
- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
CALayer *currentLayer = self.subview.layer.presentationLayer;
[self.subview.layer removeAllAnimations];
CGRect newFrame = currentLayer.frame;
self.subview.frame = currentLayer.frame;
self.large = !self.large;
if (self.large)
newFrame.size = CGSizeMake(200.0, 200.0);
else
newFrame.size = CGSizeMake(100.0, 100.0);
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.frame = newFrame;
}
completion:NULL];
}
By the way, while it generally doesn't really matter for really quick animations, for slow animations like yours, you might want to set the duration of the reversing animation to be the same as how far you've progressed in your current animation (e.g., if you're 0.5 seconds into a 3.0 second animation, when you reverse, you probably don't want to take 3.0 seconds to reverse that small portion of the animation that you have done so far, but rather just 0.5 seconds). Thus, that might look like:
- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
CFTimeInterval duration = kAnimationDuration; // default the duration to some constant
CFTimeInterval currentMediaTime = CACurrentMediaTime(); // get the current media time
static CFTimeInterval lastAnimationStart = 0.0; // media time of last animation (zero the first time)
// if we previously animated, then calculate how far along in the previous animation we were
// and we'll use that for the duration of the reversing animation; if larger than
// kAnimationDuration that means the prior animation was done, so we'll just use
// kAnimationDuration for the length of this animation
if (lastAnimationStart)
duration = MIN(kAnimationDuration, (currentMediaTime - lastAnimationStart));
// save our media time for future reference (i.e. future invocations of this routine)
lastAnimationStart = currentMediaTime;
// if you want the animations to stay relative the same speed if reversing an ongoing
// reversal, you can backdate the lastAnimationStart to what the lastAnimationStart
// would have been if it was a full animation; if you don't do this, if you repeatedly
// reverse a reversal that is still in progress, they'll incrementally speed up.
if (duration < kAnimationDuration)
lastAnimationStart -= (kAnimationDuration - duration);
// grab the state of the layer as it is right now
CALayer *currentLayer = self.subview.layer.presentationLayer;
// cancel any animations in progress
[self.subview.layer removeAllAnimations];
// set the transform to be as it is now, possibly in the middle of an animation
self.subview.layer.transform = currentLayer.transform;
// toggle our flag as to whether we're looking at large view or not
self.large = !self.large;
// set the transform based upon the state of the `large` boolean
CATransform3D newTransform;
if (self.large)
newTransform = CATransform3DMakeScale(2.0, 2.0, 1.0);
else
newTransform = CATransform3DIdentity;
// now animate to our new setting
[UIView animateWithDuration:duration
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.layer.transform = newTransform;
}
completion:NULL];
}
There is a common trick you can use to do this, but it is necessary to write a separate method to shrink (and another similar one to expand):
- (void) shrink {
[UIView animateWithDuration:0.3
animations:^{
self.myView.transform = shrinkALittleBitMatrix;
}
completion:^(BOOL finished){
if (continueShrinking && size>0) {
size=size-1;
[self shrink];
}
}];
}
So now, the trick is to break the 3 seconds animation of shrinking into 10 animations (or more than 10, of course) of 0.3 sec each in which you shrink 1/10th of the whole animation: shrinkALittleBitMatrix. After each animation is finished you call the same method only when the bool ivar continueShrinking is true and when the int ivar size is positive (the view in full size would be size=10 and the view with minimum size would be size=0). When you press the button you change the ivar continueShrinking to FALSE, and then call expand. This will stop the animation in less than 0.3 seconds.
Well, you have to fill the details but I hope it helps.
First: how to remove or cancel a animation with view?
[view.layer removeAllAnimations]
if the view have many animations, such as, one animation is move from top to bottom, other is move from left to right;
you can cancel or remove a special animation like this:
[view.layer removeAnimationForKey:#"someKey"];
// the key is you assign when you create a animation
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"someKey"];
when you do that, animation will stop, it will invoke it's delegate:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
if flag == 1, indicate animation is completed.
if flag == 0, indicate animation is not completed, it maybe cancelled、removed.
Second: so , you can do what you want to do in this delegate method.
if you want get the view's frame when the remove code excute, you can do this:
currentFrame = view.layer.presentationlayer.frame;
Note:
when you get the current frame and remove animation , the view will also animate a period time, so currentFrame is not the last frame in the device screen.
I cann't resolve this question at now. if some day I can, I will update this question.

Resuming UIView Animations on App Re-Entry?

I have 2 UIImageView's in the main view controller of my app. They are always animating via UIView animations. What I want to achieve is when the user clicks the home button, pause the UIView animations, and when the user comes back to the app, resume the animations.
So to pause I call a method from my app delegate to my ViewController to pause in the applicationWillResignActive call.
And to resume I call a method also from the app delegate to my ViewController to resume in the applicationDidBecomeActive method.
This is the code I use to either pause or resume my animations:
-(void)pauseLayer:(CALayer*)layer {
CFTimeInterval paused_time = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = paused_time;
}
-(void)resumeLayer:(CALayer*)layer {
CFTimeInterval paused_time = [layer timeOffset];
layer.speed = 1.0f;
layer.timeOffset = 0.0f;
layer.beginTime = 0.0f;
CFTimeInterval time_since_pause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - paused_time;
layer.beginTime = time_since_pause;
}
- (void)pause:(BOOL)theBool {
if (theBool) {
[self pauseLayer:self.myImageView.layer];
[self pauseLayer:self.myImageViewleft.layer];
} else {
[self resumeLayer:self.myImageView.layer];
[self resumeLayer:self.myImageViewleft.layer];
}
}
So I NSLogged everything and the methods get called accordingly however when I go to resume the animations, the image views don't resume from where they left off and are most likely at the position I placed them in the XIB (off the screen).
So when I re-enter my app, does my XIB get reloaded or something? There is absolutely nothing that should be changing the position of those UIImageView's, thats why I am really confused why this is happening.
Edit: I have also tried creating my images programmatically, no luck same issue. The pausing and resuming works successfully if I do it via buttons in my app while its running, it seems the problem is with the application delegate methods or something.
If anyone sees what's wrong, please let me know!
I found this to be really complicated and tricky so for now I will just use Cocos2D instead and make use of CCDirector's pause feature.
Edit: Just tried CCDirector's pause feature and it works perfectly. Definitely go this route!!!

Resources