Smooth Animation before NSTimer, But now it's Glitchy - ios

I have a UIImageView rotating forever on my screen... I use the code below, which I found HERE:
- (void) spinWithOptions: (UIViewAnimationOptions) options {
[UIView animateWithDuration: 0.0f
delay: 0.0f
options: options
animations: ^{
Hand.transform = CGAffineTransformRotate(Hand.transform, M_PI / 30);
}
completion: ^(BOOL finished) {
if (finished) {
if (animating) { //if, keep spinning
[self spinWithOptions: UIViewAnimationOptionCurveLinear];
} else if (options != UIViewAnimationOptionCurveEaseOut) { //final spin
//[self spinWithOptions: UIViewAnimationOptionCurveEaseOut];
}
}
}];
}
- (void) startSpin {
if (!animating) {
animating = YES;
[self spinWithOptions: UIViewAnimationOptionCurveEaseIn];
}
}
- (void) stopSpin {
animating = NO;
}
It worked find and the rotation seems smooth...
I've since added a countdown timer, using the follow:
-(void)timerTick:(NSTimer *)timer{
ticks -= 0.1; //NSLog(#"Total Ticks: %.2f",ticks);
if(ticks < 0.0){ //3 seconds is up
Failed.image = [UIImage imageNamed:#"Failed.png"];
ticks = 0.0;
[self GameOver]; //out of time...
}
else {
FailedBeforeTimer.image = [UIImage imageNamed:#"Failed Before.png"];
}
CountDownTimer.text = [NSString stringWithFormat:#"%.1f",ticks];
}
-(void)startTimer{
ticks = 3.0;
timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(timerTick:) userInfo:nil repeats:YES];
}
-(void)stopTimer{
[timer invalidate];
ticks = 0.0;
}
Since adding this timer to my app, the rotation animation is really blocky and no longer a smooth rotation...
Any ideas what I should implement to solve this?
To check, I removed the timer and re-run the application, and sure enough, the animation was smooth again :(
UPDATE:
After some more trial and error, it seems to be this line of code that slows the process down: CountDownTimer.text = [NSString stringWithFormat:#"%.1f",ticks]; ie. When I don't display the countDownTimer text on the screen/update label on screen), the rotation is smooth still...
So confused... What can I do?

Animating with zero-duration animations does not make sense. That's not how UIView animation is supposed to work. My guess is that is the source of your problems. A zero duration animation will be jumpy by definition.
You should be animating your changes over some time-period. (Always use a non-zero duration.)
EDIT:
Note that your timerTick method is horribly inefficient. You load the same image into an image view on every tick. Don't do that. Set it up with the initial image, and then only change the image when your tick count reaches zero.

You should give CADisplayLink a try, it should be suitable for your case. It's easy to use, there are heaps of tutorials out there, just do a search.

Related

Rotate UIImageView, then Reverse Rotation when 'Tapped' (Animation)

So, I've been working for hours trying to fix this, and I think It might just be missing something so obvious.
What Im trying to do is have an animation of a line (like a clock second hand) rotating around clockwise, but when the screen is pressed, it stops, but then when pressed again it starts again but the rotation reverses and the line starts rotating anticlockwise...
This is the code I used to rotate the line about a fixed point, and it works:
- (void) spinWithOptions: (UIViewAnimationOptions) options {
[UIView animateWithDuration: 0.0f
delay: 0.0f
options: options
animations: ^{
Hand.layer.anchorPoint = CGPointMake(0.5,1);
Hand.transform = CGAffineTransformRotate(Hand.transform, M_PI / 40);
}
completion: ^(BOOL finished) {
if (finished) {
if (animating) {
// if flag still set, keep spinning with constant speed
[self spinWithOptions: UIViewAnimationOptionCurveLinear];
} else if (options != UIViewAnimationOptionCurveEaseOut) {
// one last spin, with deceleration
//[self spinWithOptions: UIViewAnimationOptionCurveEaseOut];
}
}
}];
}
- (void) startSpin {
if (!animating) {
animating = YES;
[self spinWithOptions: UIViewAnimationOptionCurveEaseIn];
Clockwise = NO;
}
}
- (void) spinWithOptions_Anti: (UIViewAnimationOptions) options {
[UIView animateWithDuration: 0.0f
delay: 0.0f
options: options
animations: ^{
Hand.layer.anchorPoint = CGPointMake(0.5,1);
Hand.transform = CGAffineTransformRotate(Hand.transform, -M_PI / 40);
}
completion: ^(BOOL finished) {
if (finished) {
if (animating) {
// if flag still set, keep spinning with constant speed
[self spinWithOptions: UIViewAnimationOptionCurveLinear];
} else if (options != UIViewAnimationOptionCurveEaseOut) {
// one last spin, with deceleration
//[self spinWithOptions: UIViewAnimationOptionCurveEaseOut];
}
}
}];
}
- (void) startSpin_Anti {
if (!animating) {
animating = YES;
[self spinWithOptions_Anti: UIViewAnimationOptionCurveEaseIn];
Clockwise = YES;
}
}
- (void) stopSpin {
animating = NO;
}
So at the start of the application, the line begins rotating clockwise, when the ViewDidload calls startSpin, which calls spinWithOptions...
Sure enough, if I press the screen, the rotation animation stops, with this...
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *) event{
NSLog(#"Dircetion: %i", Clockwise);
if(animating){
[self stopSpin];
}
else{
if(Clockwise){
[self startSpin];
NSLog(#"must be clockwise");
}
else{
[self startSpin_Anti];
NSLog(#"anti");
}
}
}
But when pressed again it starts rotating as It should but never changes direction... it only ever rotates clockwise...
I just can't seem to see why it won't change direction... even the NSLogs say the bool of Clockwise is correct...

Incremental number animation

I have a homepage that showing the value of incomes and expenses. Right now the values just appear on the the screen, however is was wondering whether i can make those number start from 0 and then have an animation to increment that value, like a stopwatch. Is it also possible to make the final number after the increment has finished to animate with a pulse". This is how i am making the label pulsate now but the label becomes bigger from the left had side and not from the center.
[self.view addSubview:_lblTotal];
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scaleAnimation.duration = 0.4;
scaleAnimation.repeatCount = HUGE_VAL;
scaleAnimation.autoreverses = YES;
scaleAnimation.fromValue = [NSNumber numberWithFloat:0.8];
scaleAnimation.toValue = [NSNumber numberWithFloat:1.0];
[_lblTotal.layer addAnimation:scaleAnimation forKey:#"scale"];
The following open source UILabel subclass does the scrolling numbers bit you are looking for.
https://github.com/dataxpress/UICountingLabel
Example of the type of usage is:
myLabel.method = UILabelCountingMethodLinear;
[myLabel countFrom:50 to:100 withDuration:5.0f];
I would look to see how that is done and then extend it to add the pulse at the end once the animation for the counting has completed.
yes this is possible.
add a variable with the number to increment
#property (nonatomic) double currentValue;
#property (nonatomic, retain) NSTimer *timer;
initialize it in viewDidLoad
_currentValue = 0.00;
write a function updateNumber
-(void)updateNumber {
_currentValue = _currentValue + 0.01;
[_label setText:[NSString stringWithFormat:#"%.2f", _currentValue]];
if(_currentValue >= 100.0) { //example to stop incrementation on 100
_timer = nil; // stop the timer
// do your animation
}
}
in your viewDidAppear() or whenever you want to start the counting like this
if(_timer == nil) {
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 //seconds
target:self
selector:#selector(updateNumber)
userInfo:nil
repeats:true];
}
You might want to separate the incrementing number and the pulse animation into two different questions.
On the question of the pulse, the code is correct but the reason it scales off-center is probably due to your view's frame which is bigger than its content, which appears to be right-aligned rather than centered.
If you make _lblTotal's frame fit the content, by calling [_lblTotal sizeToFit]; your pulse animation should work as expected. But then this assumes that you do the right-alignment yourself.
Here is an example code , which will surely help you but you have to customize the timing as it certainly suit my need:-
-(void)viewDidLoad
{
currentValueAmount=0; //provide initial value for your incremented label
targetFloatAmount=1300;//provide the target value up-to which the value will be incremented.
self.lblAmount.text=[NSString stringWithFormat:#"%.2f",currentValueAmount];
[self performSelector:#selector(animateIncrementLabel) withObject:nil afterDelay:0.5f]; // give some delay as per your choice as to load the whole UI first and then calling the method to increment.
}
-(void)animateIncrementLabel
{
self.lblAmount.transform=CGAffineTransformMakeScale(0.000f, 0.000f);
timer = [NSTimer scheduledTimerWithTimeInterval: 0.001 //calling timer , if the label value is very small then increase the no of Zero's for example 0.00001
target: self
selector: #selector(handleTimer:)
userInfo: nil
repeats: YES];
//animating the incremented text with some transform and bounce
[UIView animateWithDuration:2.1 delay:0.2f usingSpringWithDamping:0.4 initialSpringVelocity:0.3 options:0 animations:^{
self.lblAmount.transform=CGAffineTransformMakeScale(1.3, 1.3);
}];
}
- (void) handleTimer: (NSTimer *) timer
{
if(currentValueAmount>targetFloatAmount)
{
self.lblAmount.text=[NSString stringWithFormat:#"%.2f",targetFloatAmount];
[timer invalidate];
timer=nil;
[UIView animateWithDuration:0.5 delay:0.2f usingSpringWithDamping:0.4 initialSpringVelocity:0.3 options:0 animations:^{
self.lblAmount.transform=CGAffineTransformMakeScale(1.0, 1.0);
}];
}
else
{
currentValueAmount=currentValueAmount+0.0008;
self.lblAmount.text=[NSString stringWithFormat:#"%.2f",currentValueAmount];
}
}

detect when UIImageView finished scrolling

I have a tall UIImageView scrolling across the screen when a button is pressed. I used a timer to change the y position of the image at a specific speed. The image is 10,000px tall.
Here's my simple code:
-(void) moveImage {
myImage.center = CGPointMake(myImage.center.x, myImage.center.y +6);
}
-(IBAction)StartGame:(id)sender{
moveImageTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:#selector(moveImage)userInfo:nil repeats:YES];
[StartGame setHidden:TRUE];
}
Here's what i've tried to stop the timer when the image has scrolled completely through the screen:
-(void) moveImage {
myImage.center = CGPointMake(myImage.center.x, myImage.center.y +6);
if ([myImage == GPointMake(myImage.center.x, myImage.center.y -568) {
[moveImageTimer invalidate];
}
That doesn't work but i thought it would. Can someone tell me why the IF statement doesn't work? Any help is appreciated, thanks
Since you want to stop when the image moves to the bottom corner.
-(void) moveImage {
CGPoint newPoint= CGPointMake(myImage.center.x, myImage.center.y +6);
myImage.center = newPoint;
if ((newPoint.y - (myImage.frame.size.width/2)) >= [UIScreen mainScreen].bounds.size.height)
{
[moveImageTimer invalidate];
}
}
you can check the condition
like if (imageView.center.y > imageView.frame.size.height - self.view.frame.size.height)
here imageView.frame.size.height - self.view.frame.size.height because the image size is twice screen size. You have to set you logic
imageView.center = CGPointMake(imageView.center.x, imageView.center.y + 6);
if (imageView.center.y > imageView.frame.size.height - self.view.frame.size.height)
{
[moveImageTimer invalidate];
}
}

NSTimer Movement irregular

I am trying to make a game movement animation in objective c so that when a button is pressed something will move across the screen while it is pressed. This works fine though the calling of the NSTimer which I am using is irregular and I am getting laggy movements. This is in iOS 6 so I wont use sprite kit as it is not available
Any suggestions on how to make it a regular call or to make it a smooth animation would be much appreciated
Thanks in advance
Here is the code:
-(void) click {
if (!self.animating) {
self.animating = YES;
timer = [NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:#selector(move)
userInfo:nil
repeats:YES];
}
}
- (void) cancelClick {
self.animating = NO;
[timer invalidate];
}
-(void) move {
CGPoint origin = self.target.frame.origin;
if ([self.direction isEqual:#"left"]) {
origin.x -= self.magnitude;
} else if ([self.direction isEqual:#"right"]) {
origin.x += _magnitude;
} else if ([self.direction isEqual:#"up"]) {
origin.y -= _magnitude;
} else {
origin.y += _magnitude;
}
[self.target move:0 toPoint:origin animated:YES delay:0 animationOptions:UIViewAnimationOptionCurveLinear];
}

Pausing CALayer animation with an animation with a delay

I have a set of nested UIView animations (2 or 3 levels deep at a given time) that I would like to be able to pause and resume. Some of these animations use -animateWithDuration:animations:completion: while others use -animateWithDuration:delay:options:animations:completion: in order to delay execution of the animation block.
I read and implemented Technical Q&A QA1673 about pausing all animations in a layer tree, but I'm encountering an issue with the animations that use a delay parameter. I can pause and resume animations just fine, but when the animation resumes, any animation block that has a delay associated with it appears to have its delay extended by the amount of time that the layer tree was paused. So for example, if one of the blocks has a delay of 1 second, and the layer tree was paused for 3 seconds, the animation delays for 4 seconds after resuming. I'm guessing this has something to do with the beginTime property? Any help would be appreciated.
// Pause and Resume methods, right from the technical Q&A
- (void)pauseAnimationsOnLayer:(CALayer *)layer
{
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;
}
- (void)resumeAnimationsOnLayer:(CALayer *)layer
{
CFTimeInterval pausedTime = [layer timeOffset];
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = 0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
}
// Chained animations
- (void)animateNextPopup
{
[UIView animateWithDuration:kRFPVictorySequenceStatePopupDuration
animations:^{
[_currentStateImageView setHidden:NO];
[_currentStateImageView setTransform:CGAffineTransformIdentity];
}
completion:^(BOOL finished) {
[UIView animateWithDuration:kRFPVictorySequenceStateSlideOffDuration
delay:kRFPVictorySequenceStateVoteDelay
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
if (winnerIsDem) {
[_currentStateImageView setFrame:CGRectMake(-_currentStateImageView.frame.size.width,
_currentStateImageView.frame.origin.y,
_currentStateImageView.frame.size.width,
_currentStateImageView.frame.size.height)];
}
else {
[_currentStateImageView setFrame:CGRectMake(1024,
_currentStateImageView.frame.origin.y,
_currentStateImageView.frame.size.width,
_currentStateImageView.frame.size.height)];
}
}
completion:^(BOOL finished) {
// Do some stuff
}
];
}
];
}
I found the solution to the problem! You have to reset the self.layer.beginTime value to zero in the completion block of your animations.
e.g.
[UIView animateWithDuration:element.duration
delay:element.delay
options:UIViewAnimationOptionCurveLinear
animations:^{
// Animate properties here!
}
} completion:^(BOOL finished){
// Reset BeginTime all the time
// So, in case a pause took place the delay values are valid again!
**self.layer.beginTime = 0.0f;**
}];
The rest of the pause / resume code stays exactly the same.
Best!
I suggest a different approach.
Animation blocks are easy to implement, but useful only if you do not need any control over your animation.
Otherwise, you should use a timer and create your own animation manually.
[NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(timerFired)
userInfo:nil
repeats:YES];
- (void)timerFired
{
if (isPaused) {
// Do nothing
} else {
// Animate
}
}
- (IBAction)pauseTapped:(id)sender
{
if (isPaused) {
isPaused = NO;
} else {
isPaused = YES;
}
}
isPaused is a flag that control your animation state.

Resources