I have an animating sine wave in one of my view controllers that appears every time an up swipe gesture is detected and disappears when a down swipe is detected. The sine wave is a UIView and the animation translates the sine wave across the screen. This works fine when I swipe up and down the first time. However, after the first swipe I get all kinds of issues. I noticed that if I reallocate and initialize the sine wave after every swipe down, I can get it to work properly, but I know thats not the correct fix. My assumption is that the frame of the sine wave has to be reset to what it was originally, but my attempt to do so didn't resolve the issue (my attempt is commented out in the code below). Any ideas?
View controller code:
- (void)viewDidLoad
{
[self setUpSine];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}
-(void) setUpSine
{
self.sineWave = [[OSDrawWave alloc] initWithFrame:CGRectMake(-1*self.view.bounds.size.width, self.view.bounds.size.height, 2*self.view.bounds.size.width, .3125*(2*self.view.bounds.size.width))];
[self.sineWave setBackgroundColor:[UIColor clearColor]];
[self.view insertSubview:self.sineWave belowSubview:self.panedView];
}
- (IBAction)handleUpSwipe:(UISwipeGestureRecognizer *)recognizer
{
[self.sineWave animateWave];
[self showSine];
[UIView animateWithDuration:.25 delay:0 options:UIViewAnimationOptionCurveLinear animations:^
{
self.sineWave.frame = CGRectOffset(self.sineWave.frame, 0, -246);
}
completion:^(BOOL finished)
{
NSLog(#"sine wave y position AFTER UP swipe %f", self.sineWave.frame.origin.y);
}];
}
- (IBAction)handleDownSwipe:(UISwipeGestureRecognizer *)recognizer
{
[self hideSine];
if(self.panedView.frame.origin.y <0)
{
[UIView animateWithDuration:.25 delay:0 options:UIViewAnimationOptionCurveLinear animations:^
{
self.sineWave.frame = CGRectOffset(self.sineWave.frame, 0, 246);
}
completion:^(BOOL finished)
{
}];
}
}
-(void) showSine
{
[self.sineWave setHidden:NO];
[self.sineWave fadeInAnimation];
}
-(void) hideSine
{
[self.sineWave fadeOutAnimation];
[self.sineWave setHidden:YES];
}
-(void) appDidEnterForeground:(NSNotification *)notification
{
[self.sineWave animateWave];
}
My sine wave is a subclass of UIView. This is the code within the sineWave class
- (void)animateWave {
[self.layer removeAllAnimations];
self.transform=CGAffineTransformIdentity;
[UIView animateWithDuration:3 delay:0.0 options: UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction animations:^{
self.transform = CGAffineTransformMakeTranslation(self.frame.size.width/2,0);
} completion:^(BOOL finished) {
self.transform = CGAffineTransformMakeTranslation(-self.frame.size.width/2, 0);
}];
}
-(void) fadeInAnimation
{
[UIView animateWithDuration:0 animations:^{
self.alpha = 1.0;}];
}
-(void) fadeOutAnimation
{
[UIView animateWithDuration:3 animations:^{
self.alpha = 0.0;}];
}
- (void)drawRect:(CGRect)rect
{
self.yc = 30;//The height of a crest.
float w = 0;//starting x value.
float y = rect.size.height;
float width = rect.size.width;
int cycles = 6;//number of waves
self.x = width/cycles;
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
CGContextSetLineWidth(context, 2);
while (w <= width) {
CGPathMoveToPoint(path, NULL, w,y/2);
CGPathAddQuadCurveToPoint(path, NULL, w+self.x/4, y/2 - self.yc, w+self.x/2, y/2);
CGPathAddQuadCurveToPoint(path, NULL, w+3*self.x/4, y/2 + self.yc, w+self.x, y/2);
w+=self.x;
}
CGContextAddPath(context, path);
[[UIColor colorWithRed:107/255.0f green:212/255.0f blue:231/255.0f alpha:1.0f]setStroke];
CGContextDrawPath(context, kCGPathStroke);
self.alpha= 0.0;
}
I was able to get this working thanks to a clue from this answer
In the view controller you need to subscribe to UIApplicationWillEnterForegroundNotification to get a notification when you app is returning from the background. I changed the viewDidLoad in the view controller and added a method to handle the notification -
- (void)viewDidLoad
{
[super viewDidLoad];
[self setUpSine];
[self.sineWave animateWave];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}
-(void) appDidEnterForeground:(NSNotification *)notification
{
[self.sineWave animateWave];
}
I also removed the call to animateWave from the handleUpSwipe: method
- (IBAction)handleUpSwipe:(UISwipeGestureRecognizer *)recognizer
{
[self showSine];
[UIView animateWithDuration:.25 delay:0 options:UIViewAnimationOptionCurveLinear animations:^
{
self.sineWave.frame = CGRectOffset(self.sineWave.frame, 0, -246);
}
completion:^(BOOL finished)
{
NSLog(#"sine wave y position AFTER UP swipe %f", self.sineWave.frame.origin.y);
}];
}
You also need to make a change to animateWave to clear any previous animation before the animation is (re)started. You don't need anything in the completion block -
- (void)animateWave {
[self.layer removeAllAnimations];
self.transform=CGAffineTransformIdentity;
[UIView animateWithDuration:3 delay:0.0 options: UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveLinear |UIViewAnimationOptionAllowUserInteraction animations:^{
self.transform = CGAffineTransformMakeTranslation(self.frame.size.width/2,0);
} completion:^(BOOL finished) {
}];
}
Related
I am trying to move image like L shape in my iOS app.
I have tried this code but it is moving image only in one line that I don't want.
- (void)startApp {
UIImageView *imageToMove =
[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"man.jpg"]];
imageToMove.frame = CGRectMake(10, 10, 100, 100);
[self.view addSubview:imageToMove];
// Move the image
[self moveImage:imageToMove
duration:1.0
curve:UIViewAnimationCurveLinear
x:70.0
y:70.0];
}
- (void)moveImage:(UIImageView *)image
duration:(NSTimeInterval)duration
curve:(int)curve
x:(CGFloat)x
y:(CGFloat)y {
// Setup the animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:duration];
[UIView setAnimationCurve:curve];
[UIView setAnimationBeginsFromCurrentState:YES];
// The transform matrix
CGAffineTransform transform = CGAffineTransformMakeTranslation(x, y);
image.transform = transform;
// Commit the changes
[UIView commitAnimations];
}
I would recommend something like this:
- (void)animateLikeL
{
[UIView animateWithDuration:1.5f
delay:0.0f
options:UIViewAnimationOptionCurveLinear
animations:^{
self.yourImage.transform = CGAffineTransformMakeTranslation(0.0f, 10.0f);
} completion:^(BOOL finished){
if(finished == NO)
{
return;
}
[UIView animateWithDuration:1.5f
delay:0.0f
options:UIViewAnimationOptionCurveLinear
animations:^{
self.yourImage.transform = CGAffineTransformMakeTranslation(10.0f, 10.0f);
} completion:^(BOOL finished){
if(finished == NO)
{
return;
}
self.yourImage.transform = CGAffineTransformIdentity;
[self animateLikeL];
}];
}];
}
Another attempt is to play arround with self.yourImage.center. Increase center.y to move the image down and then increase center.x to move it left. Hope this helps.
I did it like this: circle is a UIImage view in the interface builder size 70*70 and frame set at (10,10), then stick this code in:
- (void)viewDidAppear:(BOOL)animated {
[UIView animateWithDuration:1
delay:0
options:0
animations:^{
self.circle.frame = CGRectMake(10, 249, 70, 70);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:1
delay:0
options:0
animations:^{
self.circle.frame = CGRectMake(150, 249, 70, 70);
}
completion:^(BOOL finished) {
NSLog(#"Complete");
}];
}];
}
I would just chain the animations.
[UIView animateWithDuration:duration/2 delay:0 options:curve animations:^{
CGAffineTransform transform = CGAffineTransformMakeTranslation(x,0);
image.transform = transform;
} completion:^(BOOL finished) {
[UIView animateWithDuration:duration/2 delay:0 options:curve animations:^{
CGAffineTransform transform = CGAffineTransformMakeTranslation(x,y);
image.transform = transform;
} completion:nil];
}];
This is what I have done......Thank you.
- (void)viewDidLoad
{
[super viewDidLoad];
[self startAppForFirstImg];
[self startAppForSecondImg];
}
//First Image coordinates.
- (void)startAppForFirstImg{
UIImageView *imageToMove =
[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"man.jpg"]];
imageToMove.frame = CGRectMake(180,280,100,100);
[self.view addSubview:imageToMove];
// Move the image
[self moveFirstImage:imageToMove duration:2.0
curve:UIViewAnimationCurveLinear x:0.0 y:-200.0];
}
-(void)moveFirstImage:(UIImageView *)image duration: (NSTimeInterval)duration
curve:(int)curve x:(CGFloat)x y:(CGFloat)y{
[UIView animateWithDuration:duration/2 delay:0 options:curve animations:^{
CGAffineTransform transform = CGAffineTransformMakeTranslation(0,y);
image.transform = transform;
}
completion:^(BOOL finished) {
[UIView animateWithDuration:duration/2 delay:0 options:curve animations:^{
CGAffineTransform transform = CGAffineTransformMakeTranslation(-70,-200);
image.transform = transform;
} completion:nil];
}];
}
//Second Image coordinates.
- (void)startAppForSecondImg{
UIImageView *imageToMove =
[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"man.jpg"]];
imageToMove.frame = CGRectMake(20,20,100,100);
[self.view addSubview:imageToMove];
// Move the image
[self moveSecondImage:imageToMove duration:2.0
curve:UIViewAnimationCurveLinear x:0.0 y:290.0];
}
-(void)moveSecondImage:(UIImageView *)image duration: (NSTimeInterval)duration
curve:(int)curve x:(CGFloat)x y:(CGFloat)y{
[UIView animateWithDuration:duration/2 delay:0 options:curve animations:^{
CGAffineTransform transform = CGAffineTransformMakeTranslation(0,y);
image.transform = transform;
}
completion:^(BOOL finished) {
[UIView animateWithDuration:duration/2 delay:0 options:curve animations:^{
CGAffineTransform transform = CGAffineTransformMakeTranslation(90,290);
image.transform = transform;
} completion:nil];
}];
}
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 am trying to chain a set of UIView animateWithDuration: using the flag UIViewAnimationOptionRepeat
The animation steps I am trying to repeat are:
Animate a UIView 100 pixels to the left
Fade the UIView out (opacity '0')
While faded out (invisible), move it back to the original position (100 px to the right)
Fade the UIView back in (set opacity to '1')
Repeat the animation using flag UIViewAnimationOptionRepeat
I am not able to repeat the whole chain of animations - and only able to repeat the top most animation.
The code I have tried is this:
[UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionRepeat animations:^{
self.handImageView.frame = CGRectMoveByXPixels(self.handImageView.frame, -100);
[UIView animateWithDuration:0.5 delay:1 options:0 animations:^{
self.handImageView.alpha = 0;
} completion:^(BOOL finished) {
self.handImageView.frame = CGRectMoveByXPixels(self.handImageView.frame, 100);
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
self.handImageView.alpha = 1;
} completion:^(BOOL finished) {
}];
}];
} completion:^(BOOL finished) {
}];
The problem is that an animation that uses UIViewAnimationOptionRepeat never finishes and therefore never calls the completion block. However, you can repeat the animations using the performSelector method, as shown in the code below.
- (void)repeatingAnimationForView:(UIView *)view
{
[UIView animateWithDuration:1 delay:0 options:0
animations:^{ view.frame = CGRectMoveByXPixels(view.frame, -100); }
completion:^(BOOL finished) { if ( finished ) {
[UIView animateWithDuration:0.5 delay:0 options:0
animations:^{ view.alpha = 0; }
completion:^(BOOL finished) { if ( finished ) {
view.frame = CGRectMoveByXPixels(view.frame, 100);
[UIView animateWithDuration:0.5 delay:0 options:0
animations:^{ view.alpha = 1; }
completion:^(BOOL finished) { if ( finished ) {
if ( self.enableRepeatingAnimation )
[self performSelector:#selector(repeatingAnimationForView:) withObject:view afterDelay:0];
}}]; }}]; }}];
}
- (void)stopRepeatingAnimationForView:(UIView *)view
{
self.enableRepeatingAnimation = NO;
[view.layer removeAllAnimations];
view.frame = CGRectMake( 100, 137, 80, 50 );
view.alpha = 1;
}
- (void)startRepeatingAnimationForView:(UIView *)view
{
self.enableRepeatingAnimation = YES;
[self repeatingAnimationForView:view];
}
To stop the animations immediately, call the stopRepeatingAnimationForView method as shown above. To stop the animations at the end of a cycle, simply set self.enableRepeatingAnimation to NO.
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.
:-)
I use the following block of code to slide a UIView down and when finished rotate another UIView.
The second part of the animation, the completion block is only performed once which means the 1st animation is not completed else it would reach the completion block.
On the iphone simulator it looks as if the 1st animation did finish...
can anyone help me figure this out?
my NSLog says:
finished 1st
started 2nd
finished 1st
finished 1st
finished 1st
.
.
.
- (IBAction) move
{
[UIView animateWithDuration:0.7 animations:^{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.7];
[UIView setAnimationRepeatCount:1];
[UIView setAnimationRepeatAutoreverses:NO];
CGPoint pos = movingtTable.center;
float moveDistance = 220.0;
if(!isViewVisible){
//expose the view
pos.y = pos.y+moveDistance;
//disable selection for xy table
xTable.userInteractionEnabled = NO;
yTable.userInteractionEnabled = NO;
//angle = M_PI;
}
else
{
pos.y = pos.y-moveDistance;
xTable.userInteractionEnabled = YES;
yTable.userInteractionEnabled = YES;
//angle = -M_PI;
}
isViewVisible = !isViewVisible;
movingtTable.center = pos;
NSLog(#"finished 1st");
}completion:^(BOOL finished){
NSLog(#"started 2nd");
[UIView animateWithDuration:0.4 animations:^{
//[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.4];
//[UIView setAnimationRepeatCount:1];
//[UIView setAnimationRepeatAutoreverses:NO];
arrows.transform = CGAffineTransformMakeRotation(angle);
}completion:^(BOOL finished){
angle = -angle;
}];
}];
Why are you trying to initialize another UIView animation inside the animateWithDuration block code? Update your code to the following and make sure you're not performing multiple animations of a single view at a time.
- (IBAction) move
{
[UIView animateWithDuration:0.7 animations:^{
CGPoint pos = movingtTable.center;
float moveDistance = 220.0;
if(!isViewVisible){
//expose the view
pos.y = pos.y+moveDistance;
//disable selection for xy table
xTable.userInteractionEnabled = NO;
yTable.userInteractionEnabled = NO;
//angle = M_PI;
}
else
{
pos.y = pos.y-moveDistance;
xTable.userInteractionEnabled = YES;
yTable.userInteractionEnabled = YES;
//angle = -M_PI;
}
isViewVisible = !isViewVisible;
movingtTable.center = pos;
NSLog(#"finished 1st");
}
completion:^(BOOL finished){
NSLog(#"started 2nd");
[UIView animateWithDuration:0.4 animations:^{
arrows.transform = CGAffineTransformMakeRotation(angle);
}completion:^(BOOL finished){
angle = -angle;
}];
}];
BTW: The block code requires some serious refactoring, if you ask me :)
You are mixing and matching paradigms and I believe that is causing the issue you are seeing. You are creating an animation block, but inside of that block you are creating a new animation routine with the 'old' paradigm for running UIView animations. Apple is leading people away from the old paradigm and I would encourage you to ONLY use blocks as well.
This is why the completion block only runs once, the UIView animateWith block code only runs once. However, your internal animation code runs multiple times.
Take out:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.7];
[UIView setAnimationRepeatCount:1];
[UIView setAnimationRepeatAutoreverses:NO];
If you want your animation block to run several times, then use the full method:
animateWithDuration:delay:options:animations:completion:
Make the delay = 0, and set your options to UIViewAnimationOptionRepeat, or whatever you need to accomplish the number of cycles you want the block to complete.
Here is my suggestion assuming you want it to repeat:
- (IBAction) move
{
[UIView animateWithDuration:0.7
delay:0
options:UIViewAnimationOptionRepeat
animations:^{
CGPoint pos = movingtTable.center;
float moveDistance = 220.0;
if(!isViewVisible) {
//expose the view
pos.y = pos.y+moveDistance;
//disable selection for xy table
xTable.userInteractionEnabled = NO;
yTable.userInteractionEnabled = NO;
//angle = M_PI;
}
else {
pos.y = pos.y-moveDistance;
xTable.userInteractionEnabled = YES;
yTable.userInteractionEnabled = YES;
//angle = -M_PI;
}
isViewVisible = !isViewVisible;
movingtTable.center = pos;
NSLog(#"finished 1st");
}
completion:^(BOOL finished){
NSLog(#"started 2nd");
[UIView animateWithDuration:0.4
animations:^{
arrows.transform = CGAffineTransformMakeRotation(angle);
}
completion:^(BOOL finished){
angle = -angle;
}];
}];
}