I'm using this code to animate buttons to bounce with different delays:
- (void) animateWithDuration: (float) duration andObject: (UIButton *) buttonToAnimate{
buttonToAnimate.hidden = YES;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self animateButton: buttonToAnimate];
});
}
- (void) animateButtons
{
[self animateWithDuration:0.3 andObject:self.photoAlbum];
[self animateWithDuration:0.7 andObject:self.camera];
[self animateWithDuration: 0.45 andObject:self.helpInfo];
}
- (void) animateButton: (UIButton *) button
{
button.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.001, 0.001);
button.hidden = NO;
[UIView animateWithDuration:0.3/0.7 animations:^{
button.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.1, 1.1);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3/1.0 animations:^{
button.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.9, 0.9);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3/1.0 animations:^{
button.transform = CGAffineTransformIdentity;
}];
}];
}];
}
It works perfectly on iphone, but on ipad (simulator) the buttons makes a little jump up to the left with about 20 points on the completion block. Then back 20 point on the second completion. Using option: UIViewAnimationOptionTransitionNone (or any of the options) on the animation lessens the jump a tiny bit but you can still see it. Any idea whats wrong?
Related
I have a UIView added to a view controller. I want to make it blink twice every 5 seconds.
The following code makes the UIView blink on and off every 1 second:
-(void) showHideView{
[UIView animateWithDuration:1
delay:5
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
self.myview.alpha = 0;
}
completion:^(BOOL finished){
[UIView animateWithDuration:1
delay:5
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
self.myview.alpha = 1;
}
completion:nil
];
}
];
}
How can I make the UIView blink twice every 5 seconds? (i.e. a pulse of two blinks)
Since you've flagged this with iOS7, I might suggest keyframe animation:
[UIView animateKeyframesWithDuration:2.5 delay:0.0 options:UIViewKeyframeAnimationOptionRepeat animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.25 animations:^{
self.myview.alpha = 0.0;
}];
[UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.25 animations:^{
self.myview.alpha = 1.0;
}];
} completion:nil];
Tweak total duration (how often the cycle repeats), the relative start times (a % of the total duration), and the relative duration (again, a % of the total duration) as you see fit.
Change both of your delays to zero.
Set both durations to 1.25.
Use a timer to call the method every 2.5 seconds.
How do I use NSTimer?
Edit based on comments:
Use this instead:
-(void) showHideView {
if( self.view.alpha == 1.0 ) alpha = 0.0;
else alpha = 1.0;
}
- (void)timerCallback {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.25 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self showHideView];
});
// repeat above code for 2.5, 3.75, 5.0
}
Make your timer call timerCallback every 5 seconds.
I'd like to repeat this animation three times.
I confirmed this code worked without repeating.
[UIView animateWithDuration:1.0f
delay:0.1f
options:UIViewAnimationOptionCurveLinear
animations:^{
imageOne.center = CGPointMake(100, 150);
} completion:^(BOOL finished) {
imageOne.center = CGPointMake(100,200);
}];
Would anyone tell me how to do?
Thanks!
int animationLoopCount = 0;
- (void)doAnimation
{
for (animationLoopCount; animationLoopCount < 3; animationLoopCount++) {
[UIView animateWithDuration:1.0f
delay:0.1f
options:UIViewAnimationOptionCurveLinear
animations:^{
imageOne.center = CGPointMake(100, 150);
} completion:^(BOOL finished) {
imageOne.center = CGPointMake(100,200);
[self doAnimation];
if (animationLoopCount == 3) animationLoopCount = 0;
}];
}
}
There are a couple ways you could accomplish this, I think cleanest approach would probably be to use recursion.
Here is a general-purpose method animate things with animation blocks recursively.
- (void) animateWithDuration:(NSTimeInterval) duration
delay:(NSTimeInterval) delay
options:(UIViewAnimationOptions) options
animations:(void(^)( )) animations
completion:(void(^)( BOOL finished )) completion
repeatCount:(NSInteger) repeatCount {
// Only do the animation if we have an animations block, and our count is bigger than 0.
if ( repeatCount <= 0 || !animations )
return;
// Do the animation
[UIView animateWithDuration: duration
delay: delay
options: options
animations:^{
// Invoke the animations block
animations();
}
completion:^(BOOL finished) {
// Invoke the animation completion if it exists
if ( completion )
completion( finished );
// Invoke ourself again with a decremented repeat count.
[self animateWithDuration: duration
delay: delay
options: options
animations: animations
completion: completion
repeatCount: repeatCount - 1];
}];
}
Here is how you could use it in your code using the example you provided.
[self animateWithDuration: 1.0f
delay: 0.1f
options: UIViewAnimationOptionCurveLinear
animations: ^{
imageOne.center = CGPointMake(100, 150);
}
completion: ^(BOOL finished) {
imageOne.center = CGPointMake(100,200);
}
repeatCount: 3];
The proper way to do this is like so:
[UIView animateWithDuration:1.0f
delay:0.1f
options:UIViewAnimationOptionCurveLinear
animations:^{
[UIView setAnimationRepeatCount:3.0];// <===
imageOne.center = CGPointMake(100, 150);
} completion:^(BOOL finished) {
imageOne.center = CGPointMake(100,200);
}];
use this simple code
CABasicAnimation* animate = [CABasicAnimation animationWithKeyPath:#"position"];
[animate setDuration:1.0];
[animate setRepeatCount:3];
[animate setAutoreverses:YES];
[animate setFromValue:[NSValue valueWithCGPoint:
CGPointMake(imageOne.center.x, imageOne.center.y)]];
[animate setToValue:[NSValue valueWithCGPoint:
CGPointMake(imageOne.center.x, imageOne.center.y+100)]];
[line.layer addAnimation:animate forKey:#"position"];
I'd rather make something like this:
const CGFloat AnimationDuration = 1.0;
const CGFloat AnimationDelay = 0.1;
const NSUInteger AnimationCount = 3;
- (void) someAction
{
[self showAnimationTimes:AnimationCount];
}
- (void) showAnimationTimes:(NSUInteger)count
{
__block NSUInteger blockCount = count;
[self animation:^
{
imageOne.center = CGPointMake(100, 150);
}
completion:^(BOOL finished)
{
imageOne.center = CGPointMake(100, 200);
if (count > 1)
{
[self showAnimationTimes:--blockCount];
}
}];
}
- (void) animation:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
{
[UIView animateWithDuration:AnimationDuration
delay:AnimationDelay
options:UIViewAnimationOptionCurveLinear
animations:animations
completion:completion];
}
it's easier to understand for me.
I have a problem using a slider to trigger a series of animations, here's the code:
-(void)slideAlpha:(id)sender{
self.bigPhotoViewA.alpha = self.alphaSlider.value;
if (self.alphaSlider.value == 1){
[UIView animateWithDuration:1
animations:^{
self.alphaSlider.alpha = 0;
} completion:nil
];
[self performSelector:#selector(nextPhotoAnimation) withObject:self afterDelay:5.0 ];
}
}
-(void) nextPhotoAnimation{
self.alphaSlider.value = 0;
[UIView animateWithDuration:2
animations:^{
self.bigPhotoViewA.alpha = 0.0;
self.bigPhotoView.alpha = 0.0;
self.smallPhotoView.center = CGPointMake(startX, startY);
}
completion:^(BOOL finished) {
NSLog(#"Animation ended");
self.smallPhotoView.image = ((UIImage *)[smallImagesArray objectAtIndex:imageCount]);
}
];
}
So, when the slider reaches a value of 1, nextPhotoAnimation is launched after a delay. So far so good. The problem comes inside nextPhotoAnimation. The animations block runs ok, but the completion block runs several times every time nextPhotoAnimation is called. I get the NSLog from 6 to 9 times displayed when nextPhotoAnimation starts, and then I get it again at the right time, after 2 seconds.
I've tried to replicate the problem with simpler code and the animation/completion flow works just fine.
Try this in nextPhotoAnimation,
-(void) nextPhotoAnimation{
self.alphaSlider.value = 0;
[UIView animateWithDuration:2
animations:^{
self.bigPhotoViewA.alpha = 0.0;
self.bigPhotoView.alpha = 0.0;
self.smallPhotoView.center = CGPointMake(startX, startY);
}
completion:^(BOOL finished) {
if (finished) {
NSLog(#"Animation ended");
self.smallPhotoView.image = ((UIImage *)[smallImagesArray objectAtIndex:imageCount]);
}
}
];
}
neither you are using
[UIView beginAnimations:nil context:nil];
nor
[UIView commitAnimations];
Best way is to create a UIView and then use the protocols.call your methods using [self YOURMETHOD]; in the View.
:-)
Here's the code I'm using to take a UIImageView and make it float up and down.
[UIView animateKeyframesWithDuration:2.0 delay:0.0 options:UIViewKeyframeAnimationOptionRepeat animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.25 animations:^{
self.slider.transform = CGAffineTransformMakeTranslation(0, -5.0);
}];
[UIView addKeyframeWithRelativeStartTime:0.25 relativeDuration:0.5 animations:^{
self.slider.transform = CGAffineTransformMakeTranslation(0, 5.0);
}];
[UIView addKeyframeWithRelativeStartTime:0.75 relativeDuration:0.25 animations:^{
self.slider.transform = CGAffineTransformMakeTranslation(0, 0.0);
}];
} completion:^(BOOL finished) {
}];
However, it comes out looking like this with this delay after the animation ends and before it restarts.
How do I make it fluid?
I'm not sure what effect you're going for exactly, but I think this gives you something like your code does without the delay. I usually do this by animating a constraint, rather than using transforms. In this example, I've made an IBOutlet (topCon) to the constraint to the top of the view:
-(IBAction)floatView:(id)sender {
static int i= 1;
static float duration = .25;
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.topCon.constant = self.topCon.constant - (5 * i);
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
i = (i == 1 || i == 2)? -2 : 2;
duration = 0.5;
[self floatView:self];
}];
}
I'm trying to move a UILabel from a starting position of off the top of my UIViewController in a smooth slide type animation so that it slides down from the top when the view loads and then stops at a y position for about 10 seconds. After 10 seconds I want to slide back off the view again.
-(void) animateInstructionLabel
{
float newX = 50.0f;
float newY = 100.0f;
[UIView transitionWithView:self.lblInstruction
duration:10.0f
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, newY);
}
completion:^(BOOL finished) {
// Do nothing
}];
}
But I don't know how to do the 10 second delay and then move back within the method above. The end result is that I want this to be a label that appears like a notification and then moves off screen again.
Could someone hep me put these pieces described above together?
EDIT
I am adding this based on answer below:
-(void) animateInstructionLabel
{
float newX = lblInstruction.frame.origin.x;
float newY = 20.0f;
lblInstruction.center = CGPointMake(lblInstruction.frame.origin.x, -20.0f);
lblInstruction.bounds = CGRectMake(lblInstruction.frame.origin.x, -20.0f, 650.0f, 40.0f);
[UIView animateWithDuration:3.0f
delay:0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, newY);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:5.0f
delay:5.0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, -20);
}
completion:^(BOOL finished) {
// Do nothing
}
];
}
];
}
But even though I am setting the label.center off the screen before the animation starts I am seeing that it moves from top left corner of the viewcontroller to the center of the viewcontroller. It is not keeping the x position that it should be set to before the animation begins.
Try adding this after your animation block (oldX,oldY are the old coordinates, before animating the label in):
double delayInSeconds = 10.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[UIView transitionWithView:self.lblInstruction
duration:10.0f
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(oldX, oldY);
}
completion:^(BOOL finished) {
// Do nothing
}];
});
Something like this should work:
-(void) animateInstructionLabel {
float newX = 50.0f;
float newY = 100.0f;
labelInstruction.center = CGPointMake(newX, -20); // start off screen
[UIView animateWithDuration:10.0f
delay:0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, newY);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:10.0f
delay:10.0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, -20);
}
completion:^(BOOL finished) {
// Do nothing
}
];
}
];
}
Here is what worked in case it helps somebody else. I had to use .frame instead of .center to set the initial position and then the subsequent animation back to that position.
-(void) animateInstructionLabel
{
lblInstruction.frame = CGRectMake(lblInstruction.frame.origin.x, -40.0f, 650.0f, 40.0f);
[self.view addSubview:lblInstruction];
lblInstruction.hidden = NO;
float newX = lblInstruction.frame.origin.x;
float newY = 20.0f;
[UIView animateWithDuration:3.0f
delay:0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, newY);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:3.0f
delay:5.0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.frame = CGRectMake(lblInstruction.frame.origin.x, -40.0f, 650.0f, 40.0f);
}
completion:^(BOOL finished) {
lblInstruction.hidden = YES;
}
];
}
];
}
This smoothly makes the label slide down from off the view, stop for 5 seconds and then smoothly moves it back vertically off the view.
Thanks for the previous answers which lead me to the above final solution.