3

SITUTATION:

I have created a pulsating animation and it works.

Using the repeatForever property, I was able to make it continuous as I wanted.

But one issue remains that I wasn't able to fix after doing A LOT of research.


EXPLANATION:

The only problem is that I don't know how to add one more animation that makes the Image go back to it's original size before pulsating again.

The challenge for me resides in the fact that if I declare it after the other one, it will not be called as it will not be included in the repeatForeverloop.

Therefore, the animation I have right now is not smooth as once it completes, the image instantly goes back to it's original size before repeating itself.


WHAT HAPPENS:

1) Image pulsates to 1.2 of current size thx to animation.

THIS IS NOT SMOOTH:

2) Animation finishes at 1.2 and Image instantly "warps" back to 1.0

3) Animation repeats itself. Image pulsates to 1.2 again.


PROBLEM: How can I change my pulsatingAnimation so that my image goes back to it's original size in a smooth way before pulsating again ?


CODE:

import Foundation
import pop

class AnimationEngine {

    func pulsate(object: UIImageView) {
        let pulsateAnim = POPSpringAnimation(propertyNamed: kPOPLayerScaleXY)
        pulsateAnim.velocity = NSValue(CGSize: CGSizeMake(0.1, 0.1))
        pulsateAnim.toValue = NSValue(CGSize: CGSizeMake(1.2, 1.2))
        pulsateAnim.springBounciness = 18
        pulsateAnim.dynamicsFriction = 20
        pulsateAnim.springSpeed = 1.0
        pulsateAnim.repeatForever = true
        // pulsateAnim.autoreverses = true
        object.layer.pop_addAnimation(pulsateAnim, forKey: "layerScaleSpringAnimation")
    }
}
Coder1000
  • 4,071
  • 9
  • 35
  • 84
  • 1
    @AlessandroOrnano Great thx :) Upvote – Coder1000 May 16 '16 at 09:39
  • What does happen with `autoreverses` set to `true`? The animation works correctly but it doesn't repeat? Is that right? – Sulthan May 17 '16 at 10:30
  • @Sulthan Not exactly, when autoreverses is set to true, the animation is run in reverse. That means that it will pulsate as I want getting bigger, then it will pulsate smaller back to original size (I don't want this, I would like it to pulsate then just go back to original size smoothly without pulsating again). But the animation does repeat infinitely as wanted :) – Coder1000 May 17 '16 at 11:58

3 Answers3

3

I found the Solution.

SOLUTION:

func pulsate(object: UIImageView) {
    let pulsateAnim = POPSpringAnimation(propertyNamed: kPOPLayerScaleXY)
    let returnToSizeAnim = POPBasicAnimation(propertyNamed: kPOPLayerScaleXY)

    object.layer.pop_addAnimation(pulsateAnim, forKey: "layerScaleSpringAnimation")
    pulsateAnim.velocity = NSValue(CGSize: CGSizeMake(0.1, 0.1))
    pulsateAnim.toValue = NSValue(CGSize: CGSizeMake(1.2, 1.2))
    pulsateAnim.springBounciness = 30
    pulsateAnim.dynamicsFriction = 20
    pulsateAnim.springSpeed = 1.0
    pulsateAnim.completionBlock = {(animation, finished) in
        object.layer.pop_addAnimation(returnToSizeAnim, forKey: "layerScaleSpringAnimation")
    }
    returnToSizeAnim.toValue = NSValue(CGSize: CGSizeMake(1.0, 1.0))
    returnToSizeAnim.duration = 0.5
    returnToSizeAnim.completionBlock = {(animation, finished) in
        object.layer.pop_addAnimation(pulsateAnim, forKey: "layerScaleSpringAnimation")
    }
}

func pulsate2(object: UIImageView) {
    let pulsateAnim = POPSpringAnimation(propertyNamed: kPOPLayerScaleXY)
    let returnToSizeAnim = POPBasicAnimation(propertyNamed: kPOPLayerScaleXY)

    object.layer.pop_addAnimation(pulsateAnim, forKey: "layerScaleSpringAnimation")
    pulsateAnim.velocity = NSValue(CGSize: CGSizeMake(0.1, 0.1))
    pulsateAnim.toValue = NSValue(CGSize: CGSizeMake(1.2, 1.2))
    pulsateAnim.springBounciness = 30
    pulsateAnim.dynamicsFriction = 20
    pulsateAnim.springSpeed = 1.0
    pulsateAnim.completionBlock = {(animation, finished) in
        object.layer.pop_addAnimation(returnToSizeAnim, forKey: "layerScaleSpringAnimation")
    }
    returnToSizeAnim.toValue = NSValue(CGSize: CGSizeMake(1.0, 1.0))
    returnToSizeAnim.duration = 0.5
    returnToSizeAnim.completionBlock = {(animation, finished) in
        object.layer.pop_addAnimation(pulsateAnim, forKey: "layerScaleSpringAnimation")
    }
}

N.B.:

I need to declare one pulsate function for each object I want to use it on. If I don't do that, the second time I call the function, it won't work properly because an instance of the animations is already running.

Coder1000
  • 4,071
  • 9
  • 35
  • 84
1

I dont know Facebook pop , so I speak only about the analysis and logic to how to implement this feature (pulse effect)

As I've write in comments, what you want to do seems exactly what happened many times in SpriteKit where you , to make a specific animation (SKAction), have to build a series of actions.

Just to make a SpriteKit example, this is what I do to make a specific pulse effect:

let titleForward = runTitleForward()
let wait = SKAction.waitForDuration(5.0)
let titleBackward = runTitleBackward()
let titleAnim = SKAction.repeatActionForever((SKAction.sequence([titleForward, wait, titleBackward,wait ])))
title.runAction(titleAnim, withKey:"titleAnimation")

func runTitleForward()-> SKAction {
        let atlascTexturesArray = self.myManager.atlascTexturesDictionary["title-anim"]
        let texturesAction = SKAction.animateWithTextures(atlascTexturesArray!,timePerFrame: 0.09)        
        return texturesAction
    }

func runTitleBackward()-> SKAction {
    let texturesAction = runTitleForward()
    let texturesReverseAction = SKAction.reversedAction(texturesAction)
    return texturesReverseAction()
}

Hope you help to do the same thing in UIKit.

UPDATE: (I don't test it..)

func delayAnimation(duration:NSTimeInterval)-> CABasicAnimation {
   let animation : CABasicAnimation = CABasicAnimation(keyPath: "opacity");
   animation.delegate = self
   animation.fromValue = 1 
   animation.toValue = 1  // fake animation, just to obtain duration delay..
   animation.duration = duration
   return animation
}

func pulseElements () -> (POPSpringAnimation,POPSpringAnimation) {

        // zoom in
        let pulsateInAnim = POPSpringAnimation(propertyNamed: kPOPLayerScaleXY)
        pulsateInAnim.velocity = NSValue(CGSize: CGSizeMake(0.1, 0.1))
        pulsateInAnim.toValue = NSValue(CGSize: CGSizeMake(1.2, 1.2))
        pulsateInAnim.springBounciness = 18
        pulsateInAnim.dynamicsFriction = 20
        pulsateInAnim.springSpeed = 1.0

        // zoom out
        let pulsateOutAnim = POPSpringAnimation(propertyNamed: kPOPLayerScaleXY)
        ...

        return (pulsateInAnim,pulsateOutAnim)
}

func pulseAnimation() {
     let (pulsateInAnim,pulsateOutAnim) = pulseElements()
     let wait = delayAnimation(1.0)
     var pulseAnimationGroup: CAAnimationGroup = CAAnimationGroup()
     pulseAnimationGroup.animations = [pulsateInAnim, wait,  pulsateOutAnim, wait]
     pulseAnimationGroup.duration = 0.5
     pulseAnimationGroup.repeatCount = Float.infinity
     object.layer.addAnimation(pulseAnimationGroup, forKey: layerScaleSpringAnimation)
}
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
  • 1
    Sadly, I was not able to reproduce this in facebook pop, but it certainly helped, thank you ! – Coder1000 May 16 '16 at 18:47
  • It's hard, is it possible to have POPSpringAnimation in block? completion to make two animation sequentially . And after, have a method like: animationWithanimation to create a new animation with the sequence to repeatforever it... – Alessandro Ornano May 16 '16 at 18:52
  • I thought of the completion block, but I don't know how to create a new animation with the sequence to then repeat it :( – Coder1000 May 16 '16 at 19:26
1

I'm a huge ReactiveCocoa nerd so I built a class around POPAnimation so I could deal with animations in a more functional manner.

import Foundation
import ReactiveCocoa
import pop
import Result

/**
 `UIView` wrapper for performing reactive-based pop animations.
 */
public struct ReactivePOPView<View: UIView> {

    /// View object
    private weak var view: View?
    /// `SignalProducer` object when the wrapper is about to deallocate.
    private let willDealloc: SignalProducer<Void, NoError>

    /// Wrapper on the view property.
    private var animator: View? { return self.view }

    /**
     Create an animation wrapper with a view.

     - parameter view: `UIView` object.
     */
    public init(_ view: View) {
        self.view = view
        self.willDealloc = view
            .willDeallocSignalProducer()
        }
}

/**
 Binds a `Signal` that emits an animation object to a `ReactivePOPView`.

 - parameter view:   `ReactivePOPView` object.
 - parameter signal: `Signal` object.

 - returns: `Disposable` object.
 */
 public func <~ <T, Animation: POPAnimation>(view: ReactivePOPView<T>, signal: Signal<Animation, NoError>) -> Disposable {
    let disposable = CompositeDisposable()
    let viewDisposable = view.willDealloc.startWithCompleted {
        disposable.dispose()
    }
    disposable.addDisposable(viewDisposable)

    let signalDisposable = signal.observe(Observer(next: {
        view.animator?.pop_addAnimation($0, forKey: $0.name ?? "")

    }, completed: {
        disposable.dispose()
    }))
    disposable.addDisposable(signalDisposable)
    return disposable
}

/**
 Binds a `SignalProducer` that emits an animation object to a `ReactivePOPView`.

 - parameter view:     `ReactivePOPView` object.
 - parameter producer: `SignalProducer` object.

 - returns: `Disposable` object.
*/
public func <~ <T, Animation: POPAnimation>(view: ReactivePOPView<T>, producer: SignalProducer<Animation, NoError>) -> Disposable {
    var disposable: Disposable!
    producer.startWithSignal { signal, signalDisposable in
        view <~ signal
        disposable = signalDisposable

        view.willDealloc.startWithCompleted {
            signalDisposable.dispose()
        }
    }
    return disposable
}

/**
 Bind a reactive animation property to a `ReactivePOPView` property.

 - parameter destinationProperty: `ReactivePOPView` property.
 - parameter sourceProperty:      Animation property.

 - returns: `Disposable` object.
 */
public func <~ <T, P: PropertyType where P.Value == POPAnimation>(destinationProperty: ReactivePOPView<T>, sourceProperty: P) -> Disposable {
    return destinationProperty <~ sourceProperty.producer
}

With this class, you can do things like:

ReactivePOPView<UIButton>(self.loginButton) <~ self.someSignal.signal
.flatMap(.Concat) { _ in SignalProducer<POPBasicAnimation, NoError> in
    let animation ...
    // construct animation here
    return SignalProducer(value: animation)
}
.repeat(0)

I also have a wrapper class for constraints if you want that.

barndog
  • 6,975
  • 8
  • 53
  • 105
  • That's great, but can I pass 2 animations at once, like: `return SignalProducer(value: animation1, animation2)` ? – Coder1000 May 19 '16 at 11:15
  • Is there really no way to do what I am looking for using only what facebook Pop already offers ? – Coder1000 May 19 '16 at 11:16
  • Sure you can create two separate animation signals and do `SignalProducer(values: [firstAnimationProducer, secondAnimationProducer]).flatten(.Merge)`. That will apply both animations as they fire. Or you could do `SignalProducer(values: [someTrigger.flatMap(.Latest) { _ in animationSignal }, otherTrigger.flatMap(.Latest) { _ in secondAnimation }]).flatten(.Merge)` And there may be, I don't know. I'm not an expert on pop, this was just a way I simplified using the animations and reactive cocoa. – barndog May 19 '16 at 11:44