1

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;
}
Brosef
  • 2,945
  • 6
  • 34
  • 69
  • You should probably call setupSine from `viewDidLoad` rather than `viewDidAppear` that way it is only initialised and added once – Paulw11 Aug 07 '14 at 00:15
  • When I move setUpSine to `viewDidLoad` the sine wave will work when the view controller loads the first time. But if I navigate to another view controller, then back to the view controller with the sine wave, it will no longer load. – Brosef Aug 07 '14 at 00:20
  • I'm thinking setUpSine needs to be called every time I return to the view controller and when I bring the app to the foreground from background. – Brosef Aug 07 '14 at 00:22
  • You never remove `self.sineView` so it will remain as a subview until the view controller is disposed of. There is no need to re-add it every time. – Paulw11 Aug 07 '14 at 00:25
  • Every time I swipe up I make sure self.sineWave != nil and it checks out. I removed the animations to make sure there wasn't an issue there but it the problem still persists. Every time i leave the view controller and return self.sineWave stops appearing. – Brosef Aug 07 '14 at 00:56
  • OK when the call to [self.sineWave animateWave] is commented out, the sine wave appears. Anything look wrong in that method? – Brosef Aug 07 '14 at 01:14

1 Answers1

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) {
    }];
}
Community
  • 1
  • 1
Paulw11
  • 108,386
  • 14
  • 159
  • 186
  • Almost working right. So the animation is appearing every time now. However, it only works properly on my first up swipe. A second up swipe still shows the animation but the animation is out of sync. If I leave the view controller then return and do an up swipe, the animation works again, until the second up swipe... – Brosef Aug 07 '14 at 22:57
  • Also, I just realised there is another bug - If you swipe up when the wave is already up it goes up again. There needs to be a boolean property that tracks the up/down state. – Paulw11 Aug 07 '14 at 23:04
  • Good call on the double up swipe. I'll get that fixed. I'm able to do consecutive up swipes now by calling `[self.sineWave.layer removeAllAnimations];` in the completion block of every down swipe. The only thing not working is hitting the home button then reopening the app. The sine wave appears, but the animation is out of sync. – Brosef Aug 07 '14 at 23:16
  • Hmm. That works for me and I don't have anything in the completion block of the downside. The only thing I have removed is the check for the panedView value as I didn't have that view in the code I put together – Paulw11 Aug 07 '14 at 23:17
  • I got it working by creating a BOOL value to detect if the view has been swiped up like you suggested. Then in `appDidEnterForeground` I used an if else statement. If the view is up when I exit the app call `[self.sineWave animateWave];` else call `[self.sineWave.layer removeAllAnimations];`. I have to call `[self.sineWave.layer removeAllAnimations]` on every down swipe. Animation won't work otherwise. I also call `[self.sineWave animateWave];` on every up swipe. – Brosef Aug 07 '14 at 23:36
  • My app doesn't need that and it works perfectly every swipe. I will upload my project shortly and you can compare. – Paulw11 Aug 07 '14 at 23:39
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/58920/discussion-between-brosef-and-paulw11). – Brosef Aug 07 '14 at 23:43