0

I am trying to have a image of a star "breathe" in a loop. Getting bigger and then smaller and then repeating itself. I want this to continue while my app is still running and being responsive. And when I go to another view in my app , then I want the animation to stop.

Here is what I got

    override func viewDidLoad() {
    super.viewDidLoad()

    DispatchQueue.main.async {
            self.animateStars()
    }
}

func animateStars() {
  UIView.animate(withDuration: 4, animations : {
        self.star.transform = CGAffineTransform(scaleX: 2.5, y: 2.5)

    }) { finished in
        NSLog("Star is big. Finished = \(finished)")
    }

    UIView.animate(withDuration: 4, animations : {
        self.star.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)

    }) { finished in
        NSLog("Star is small. Finished = \(finished)")
    }
}

This does not loop tough. And I dont know how to make it loop without it running like mad in the background, when the user click on a button with segue, to go to another view.

I have found one solution in Objective C, but I cant seem to understand how I should translate it to Swift 3.0. Example here: https://stackoverflow.com/a/31978173/5385322

 [UIView animateWithDuration:1
                  delay:0
                options:UIViewKeyframeAnimationOptionAutoreverse | UIViewKeyframeAnimationOptionRepeat
             animations:^{
                 yourView.transform = CGAffineTransformMakeScale(1.5, 1.5);
             }
             completion:nil];

Thank you for your time and suggestions! //Simon

Community
  • 1
  • 1
  • `UIView.animate(withDuration: 1, delay: 0, options: [.autoreverse, .repeat], animations: { self.star.transform = CGAffineTransform(scaleX: 2.5, y: 2.5) })` – beyowulf Mar 05 '17 at 15:44
  • This worked absolutly great! One problem tough, that I'm not sure is related to this. But when I go to another view, and then press back to get back to the first view (with the animation), the animation is frozen and does not function. Any idea how to solve that so that the animation will run each time I enter this main view? – Simon Riemertzon Mar 05 '17 at 17:26
  • Why did I get a -1? :) – Simon Riemertzon Mar 06 '17 at 14:20
  • I have no idea why you got a -1. It wasn't me. In your viewWillDisappear you should call removeAllAnimations and set the transform back to identity. Then in your viewWillAppear you should re-invoke the animation. – Duncan C Mar 06 '17 at 14:31

3 Answers3

7

The Swift version of the Objective-C repeating animation code you posted would be:

UIView.animate(withDuration: 1.0,
               delay: 0,
               options: [.autoreverse, .repeat, .allowUserInteraction],
               animations: {
                self.star.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
},
               completion: nil

)

(In Swift, most option flag parameters like the options parameter in that animation code are of type OptionSet, which is bit flags that supports Swift Set syntax, so you list the options you want in square brackets, separated by commas.

Note that @TungFam's solution would not work as written, but you COULD use that approach if you used a completion block that checked an animate boolean and re-invoked the animation method if it was true.

EDIT:

If you want to stop the animation, use

self.star.layer.removeAllAnimations()
self.star.layer.transform = CGAffineTransform.identity

EDIT #2:

If you want to be able to start and stop an animation at will, you can use code like this:

func animateView(_ view: UIView, animate: Bool) {
  if animate {
    UIView.animate(withDuration: 1.0,
                   delay: 0,
                   options: [.autoreverse, .repeat, .allowUserInteraction],
                   animations: {
                    view.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
    },
                   completion: nil

    )
  } else {
    self.button.layer.removeAllAnimations()
    UIView.animate(withDuration: 1.0,
                   delay: 0,
                   options: [.curveEaseOut, .beginFromCurrentState, .allowUserInteraction],
                   animations: {
                    view.transform = CGAffineTransform.identity
    },
                   completion: nil

    )
  }
}

That code will cause the view to smoothly animated back to normal size when the animation is stopped. It also adds the .allowUserInteraction flag to the animation, in case the view you're animating is a button that you want to be able to tap on while it's size is changing.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • With some slight changes to suit my own code... This worked wonders! Thank you kindly. Absolutly boss work! I will answer my own question with what I finally ended up with. :) – Simon Riemertzon Mar 05 '17 at 19:04
  • Oh sorry, right! There you go. If you were the one putting a -1 on my question i hope this will change that to a +1 :) – Simon Riemertzon Mar 06 '17 at 14:20
  • Duncan C ... Your code made my button animate but now it is not clickable. Any suggestions? –  Jan 11 '19 at 18:55
  • Make sure you use the `.allowUserInteraction` property in all your animations. In my code I didn't include that option in the 2nd animation. – Duncan C Jan 11 '19 at 19:27
0

Try to add the variable that holds the bool value that says either it needs to animate or not. and change it when user taps the button to go to next screen.

Something like this:

var animate = true

override func viewDidLoad() {
   super.viewDidLoad()

   self.animateStars()
}

func animateStars() {
  while animate == true   {
    // breathing animation
  }
}

@IBAction func buttonPressed(_ sender: UIButton) {
   animate = false

   // perform your segue
}

if you want to animate it back again when the user comes back to this screen then place self.animateStars() to the viewWillAppear method.

Tung Fam
  • 7,899
  • 4
  • 56
  • 63
  • That's not quite right. You would need to use the `animate(withDuration:delay:options:animations:completion:)` form of the animation call, and use the completion handler to re-invoke the animation method if the `animate` flag is still true. – Duncan C Mar 05 '17 at 17:16
  • @DuncanC agree with you. didn't check the code of animating. thought the issue is how to stop animating before going to another VC. thanks – Tung Fam Mar 05 '17 at 17:27
  • @DuncanC , can you perhaps explain this a little more throughly. TungFam's solution unfortunatly resulted in a unresonsive app and an infinate loop since the animation suggested in a comment earlier by: beyowulf, that works, makes it repeat itself. So the animate variable never becomes false. – Simon Riemertzon Mar 05 '17 at 17:50
  • @SimonRiemertzon i read this "This worked absolutly great! One problem tough, that I'm not sure is related to this. But when I go to another view, and then press back to get back to the first view (with the animation), the animation is frozen and does not function. Any idea how to solve that so that the animation will run each time I enter this main view?" --- the conclusion is just call `self.animateStars()` in `viewWillAppear `. disregard my answer with while loop. – Tung Fam Mar 05 '17 at 18:04
  • 1
    @SimonRiemertzon, see my answer. – Duncan C Mar 05 '17 at 18:19
0

With some insights from @DuncanC I was able to get this to work quite well. The animation now stops when I go to another view and continues when I get back to the original view.

The code looks like this:

class MainViewController: UIViewController {

@IBOutlet weak var star: UIImageView!

var queue = DispatchQueue.main

  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    self.star.layer.removeAllAnimations()
    self.star.transform = CGAffineTransform.identity

    queue.async {
        self.animateStars()
    }
}
    @IBAction func someAction(_ sender: Any) {
    performSegue(withIdentifier: "someSegue", sender: self)
    }

    func animateStars() {
    UIView.animate(withDuration: 1,
                   delay: 0,
                   options:[.autoreverse, .repeat],
                   animations : {
                    self.star.transform = CGAffineTransform(scaleX: 2.5, y: 2.5)})
    { finished in
        UIView.animate(withDuration: 1.0,
                       delay: 0,
                       options: [.curveEaseOut, .beginFromCurrentState],
                       animations: {
                        self.star.transform = CGAffineTransform.identity
        },
                       completion: nil
        )
    }
    UIView.animate(withDuration: 1, 
                   delay: 0, 
                   options: [.autoreverse, .repeat], 
                   animations: { self.star.transform = CGAffineTransform(scaleX: 2.5, y: 2.5) })
}
}

I'm very new to programming still, going to school for it, and so far I havent learned about testing that much. Because of that, I havent tested this for memoryleaks and whatnots. But the code works and that means I'm happy for the time being.

Thank you all for your help! And do come with suggestions if your think you can improve on the code. :)

//Simon