Incremental number animation - ios

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];
}
}

Related

Smooth Animation before NSTimer, But now it's Glitchy

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.

How to reduce the opacity of an UIButton gradually?

In my project i have an UIButton which on clicking zooms. Everything is fine till here but i require the opacity of the button to gradually decrease from 1 to 0. I mean it should decrease in 1,0.9,0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0 sequence. My current code is
-(void)roundButtonAnimation
{
self.buttonZoom = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
self.buttonZoom.duration = 0.8f;
self.buttonZoom.values = #[[NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(2,2,2)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(3,3,3)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(5,5,5)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(6,6,6)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(7,7,7)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(9,9,9)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(10,10,10)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(11,11,11)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(12,12,12)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(13,13,13)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(14,14,14)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(15,15,15)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(16,16,16)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(17,17,17)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(18,18,18)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(50,50,50)]];
self.buttonZoom.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
self.roundButton.transform = CGAffineTransformMakeScale(0,0);
self.roundButton.alpha = 0.3;
[self.roundButton.layer addAnimation:self.buttonZoom forKey:#"buttonScale"];
[self.CLSpinnerView1 stopAnimating];
//[self performSelector:#selector(secondView) withObject:self afterDelay:0.3];
[self performSelector:#selector(postNotification) withObject:self afterDelay:0.6];
}
[UIView animateWithDuration:1.0
delay:0.0
options: UIViewAnimationCurveLinear
animations:^{
self.roundButton.transform = CGAffineTransformMakeScale(50.0f, 50.0f);
self.roundButton.alpha = 0.0;
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
}
Let's explain a little, in this animation block you say that the animation should take place in one second, during this second based on a linear curve (thus always equal increment/decrement) the button should zoom 50 times and alpha should be animate to 0.
I do not understand why you are using a more complicated CAKeyframeAnimation over that simple animation block.
Keyframe animation are useful when you want more power over timings and animate over a set of values.
UIView animateWithDuration:animations: should serve your purpose here.
[UIView animateWithDuration:1.0 animations:^{
button.alpha = 0.0;
}];
Will gradually change the button's alpha to 0 over one second (...WithDuration:1.0...).
See the The UIView Class Reference for more information.
Hope this helps.
Try this
[button.layer removeAnimationForKey:#"opacity"];

How to save Best Time score using NSUserDefault

Hey guys I'm extremely new to programming and especially with Objective C so please bear with me. I been practicing my skills on an iPhone app project I have been working on and now am stuck on an issue with saving time. I have a simple slider puzzle game that has a timer that increases until you finish the puzzle. the time is represent in this format "00:00". i would like to be able to save the lowest time after completion and present it on the screen on the start of the app for reference on the time to beat. The problem I face however is that it keeps saving the time 0 as the lowest and nothing can beat that.
Below is code of my:
Timer
-(void)setTimer
{
self.secondsCount = 0;
self.gameCountDownTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerRun) userInfo:nil repeats:YES];
}
-(void)timerRun
{
self.secondsCount++;
NSString *timerOutput = [NSString stringWithFormat:#" %02.f : %02i", round(self.secondsCount / 60), self.secondsCount % 60 ];
self.timeLabel.text = timerOutput;
}
StartGame Button
- (IBAction)start:(id)sender {
// reset steps
self.secondsCount=0;
step = 0;
[self setTimer];
// set the view so that it can interact with touches
[board setUserInteractionEnabled:YES];
// set the label text to 'reset'
[startButton setTitle:#"Reset" forState:UIControlStateNormal];
// initialize the board, lets play!
[board playWithImage:image andSize:(boardSize.selectedSegmentIndex+3)];
}
End of Game
(void)puzzleBoardDidFinished:(PuzzleGameView *)puzzleBoard {
// add the full image with 0.0 alpha in view
if(self.secondsCount!=0){
self.finalTime = self.secondsCount;
NSLog(#"seconds is: %i",self.secondsCount);
[self saveTime];
if(self.finalTime<self.bestTime) {
NSLog(#"bestTime! is: %i",self.finalTime);
[self saveTime];
}
}
[self.gameCountDownTimer invalidate];
self.gameCountDownTimer = nil;
UIImageView *fullImage = [[UIImageView alloc]initWithImage:image];
fullImage.frame = board.bounds;
fullImage.alpha = 0.0;
[board addSubview:fullImage];
[UIView animateWithDuration:.4
animations:^{
// set the alpha of full image to 1.0
fullImage.alpha = 1.0;
}
completion:^(BOOL finished){
// set the view interaction and set the label text
NSLog(#"Congrats! You finish this %d x %d puzzle with %d steps", (boardSize.selectedSegmentIndex+3), (boardSize.selectedSegmentIndex+3), step);
[board setUserInteractionEnabled:NO];
[startButton setTitle:#"Start" forState:UIControlStateNormal];
}];
}
And Attempt to save the data
-(void)saveTime {
[[NSUserDefaults standardUserDefaults]setInteger:self.secondsCount forKey:#"BestTime"];
[[NSUserDefaults standardUserDefaults]synchronize];
}
This was in my viewdidload
self.bestTime = [[NSUserDefaults standardUserDefaults]integerForKey:#"BestTime"];
self.bestTimeLabel.text = [NSString stringWithFormat:#" %02.f : %02i", round(self.bestTime / 60), self.bestTime % 60 ];
As stated before Im pretty new so any advice and recommendations would be awesome. Im definitely eager to learn more about this stuff and would greatly appreciate if you guys could help with the journey. Thank You
It looks like [[NSUserDefaults standardUserDefaults]integerForKey:#"BestTime"] is 0 every time you assign its value to self.bestTime.
This code might help:
if(self.bestTime == 0){
self.bestTime = finalTime;
}
Your code looks ok. I'm pretty sure it's just the showing the value in NSLog that is not working.
Change:
self.bestTimeLabel.text = [NSString stringWithFormat:#" %02.f : %02i", round(self.bestTime / 60), self.bestTime % 60 ];
To:
self.bestTimeLabel.text = [NSString stringWithFormat:#" %02.f : %02i", round(self.bestTime / 60.0f), self.bestTime % 60 ];
For float variable: int / int yields integer which you are assigning to float and printing it so it's 0.00.

Creating slow scrolling to indexPath in UICollectionView

I'm working on a project where I'm using a UICollectionView to create an 'image ticker' where I'm advertising a series of logos. The collectionView is one item high and twelve items long, and shows two to three items at a time (depending on size of the logos visible).
I would like to make a slow automatic scrolling animation from the first item to the last, and then repeat.
Has anyone been able to make this work? I can get the scrolling working using
[myCollection scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:(myImages.count -1) inSection:0] atScrollPosition:UICollectionViewScrollPositionRight animated:YES];
But this is way too fast!
[UIView animateWithDuration:10 delay:2 options:(UIViewAnimationOptionAutoreverse + UIViewAnimationOptionRepeat) animations:^{
[myCollection scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:(myImages.count -1) inSection:0] atScrollPosition:UICollectionViewScrollPositionRight animated:NO];
} completion:nil];
This yields the desired scrolling speed, but only the last few cells are visible in the series. I suspect they (and even the starting visible cells) are being dequeued immediately.
Any thoughts?
You can try this approach:
#property (nonatomic, assign) CGPoint scrollingPoint, endPoint;
#property (nonatomic, strong) NSTimer *scrollingTimer;
#synthesize scrollingPoint, endPoint;
#synthesize scrollingTimer;
- (void)scrollSlowly {
// Set the point where the scrolling stops.
self.endPoint = CGPointMake(0, 300);
// Assuming that you are starting at {0, 0} and scrolling along the x-axis.
self.scrollingPoint = CGPointMake(0, 0);
// Change the timer interval for speed regulation.
self.scrollingTimer = [NSTimer scheduledTimerWithTimeInterval:0.015 target:self selector:#selector(scrollSlowlyToPoint) userInfo:nil repeats:YES];
}
- (void)scrollSlowlyToPoint {
self.collectionView.contentOffset = self.scrollingPoint;
// Here you have to respond to user interactions or else the scrolling will not stop until it reaches the endPoint.
if (CGPointEqualToPoint(self.scrollingPoint, self.endPoint)) {
[self.scrollingTimer invalidate];
}
// Going one pixel to the right.
self.scrollingPoint = CGPointMake(self.scrollingPoint.x, self.scrollingPoint.y+1);
}
I use a "1 pixel offset" trick. When scrolling programmatically, just set the end contentOffset.x to 1 pixel more/less than it should. This should be unnoticeable. And in completion block set it to actual value. That way you can avoid the premature cell dequeuing problem and get smooth scrolling animation ;)
Here is an example of scrolling to the right page (e.g. from page 1 to 2). Notice that in the animations: block I actually scrolls one pixel less (pageWidth * nextPage - 1). I then restore the correct value in the completion: block.
CGFloat pageWidth = self.collectionView.frame.size.width;
int currentPage = self.collectionView.contentOffset.x / pageWidth;
int nextPage = currentPage + 1;
[UIView animateWithDuration:1
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
[self.collectionView setContentOffset:CGPointMake(pageWidth * nextPage - 1, 0)];
} completion:^(BOOL finished) {
[self.collectionView setContentOffset:CGPointMake(pageWidth * nextPage, 0)];
}];
You could also have a "slow" scroll to the end of you UICollectionView without having to "jump" from indexpath to indexpath. I created this quickly for a collection view on a TVOS app:
func autoScroll () {
let co = collectionView.contentOffset.x
let no = co + 1
UIView.animateWithDuration(0.001, delay: 0, options: .CurveEaseInOut, animations: { [weak self]() -> Void in
self?.collectionView.contentOffset = CGPoint(x: no, y: 0)
}) { [weak self](finished) -> Void in
self?.autoScroll()
}
}
I just call the autoScroll() in the viewDidLoad() and the rest takes care of it itself. The speed of the scrolling is decided by the animation time of the UIView. You could (I haven't tried) add an NSTimer with 0 seconds instead so you can invalidate it on userscroll.
For anyone else finding this, I've updated Masa's suggestion to work on Swift and I've introduced a little bit of easing towards the end so it acts more like the original scrollItemsToIndexPath animated call. I have hundreds of items in my view so a steady pace wasn't an option for me.
var scrollPoint: CGPoint?
var endPoint: CGPoint?
var scrollTimer: NSTimer?
var scrollingUp = false
func scrollToIndexPath(path: NSIndexPath) {
let atts = self.collectionView!.layoutAttributesForItemAtIndexPath(path)
self.endPoint = CGPointMake(0, atts!.frame.origin.y - self.collectionView!.contentInset.top)
self.scrollPoint = self.collectionView!.contentOffset
self.scrollingUp = self.collectionView!.contentOffset.y > self.endPoint!.y
self.scrollTimer?.invalidate()
self.scrollTimer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "scrollTimerTriggered:", userInfo: nil, repeats: true)
}
func scrollTimerTriggered(timer: NSTimer) {
let dif = fabs(self.scrollPoint!.y - self.endPoint!.y) / 1000.0
let modifier: CGFloat = self.scrollingUp ? -30 : 30
self.scrollPoint = CGPointMake(self.scrollPoint!.x, self.scrollPoint!.y + (modifier * dif))
self.collectionView?.contentOffset = self.scrollPoint!
if self.scrollingUp && self.collectionView!.contentOffset.y <= self.endPoint!.y {
self.collectionView!.contentOffset = self.endPoint!
timer.invalidate()
} else if !self.scrollingUp && self.collectionView!.contentOffset.y >= self.endPoint!.y {
self.collectionView!.contentOffset = self.endPoint!
timer.invalidate()
}
}
Tweaking the values of dif and modifier adjusts the duration/level of ease for most situations.
In .h file,
declare this timer,
NSTimer *Timer;
Place this timer code,
[CollectionView reloadData];
Timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(AutoScroll) userInfo:nil repeats:YES];
then,
#pragma mark- Auto scroll image collectionview
-(void)AutoScroll
{
if (i<[Array count])
{
NSIndexPath *IndexPath = [NSIndexPath indexPathForRow:i inSection:0];
[self.ImageCollectionView scrollToItemAtIndexPath:IndexPath
atScrollPosition:UICollectionViewScrollPositionRight animated:NO];
i++;
if (i==[Array count])
{
i=0;
}
}
}
Hope it'll be useful.

animating in 3 steps one after the other ios

I have 3 cards. at first all cards move to the right, then after 1 sec card2 would appear on the left side of the view, and then after some more second card2 moves to the right. I implemented this using the following code. when the view loads I have the following code,
counter = 0;
timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(performAnimationToRight) userInfo:nil repeats:YES];
here is the implementation of the performAnimationToRightMethod
-(void)performAnimationToRight
{
if (counter == 0) {
CGAffineTransform transformToLeft = CGAffineTransformMakeTranslation(388,0);
[UIView beginAnimations:#"Move1" context:nil];
[UIView setAnimationDuration:2];
card0.transform = transformToLeft;
card1.transform = transformToLeft;
card2.transform = transformToLeft;
[UIView commitAnimations];
}else if(counter == 50){
card2.frame = CGRectMake(-300,card2.frame.origin.y, card2.frame.size.width, card2.frame.size.height);
}else if(counter == 200){
timer = nil;
counter = 0;
CGAffineTransform transformToLeft = CGAffineTransformMakeTranslation(-300,0);
[UIView beginAnimations:#"Move2" context:nil];
[UIView setAnimationDuration:2];
card2.transform = transformToLeft;
[UIView commitAnimations];
}
counter++;
}
the problem is that on the last action card2 instead of moving to the right moves to the left, and that's wether or not I use,
CGAffineTransform transformToLeft = CGAffineTransformMakeTranslation(-300,0);
or
CGAffineTransform transformToLeft = CGAffineTransformMakeTranslation(+300,0);
Two comments:
First, you are moving your card2 to the left at counter == 50 by setting it's frame origin to be (probably) left of your starting position.
Second, this would be easier to implement using a CAKeyframeAnimation. You wouldn't have to manage your NSTimer and the counter mechanism.

Resources