Quiero bamboleo una imagen de ida y vuelta en mi aplicación similar a cómo los iconos del iPhone bamboleo cuando se presiona hacia abajo en él. ¿Cuál es la mejor manera de hacerlo?

Esta es mi primera incursión en las animaciones que no utiliza un GIF animado. Creo que la idea es ligeramente girar la imagen hacia atrás y adelante para crear el efecto de bamboleo. He mirado en el uso de CABasicAnimation y CAKeyframeAnimation. CABasicAnimation crea una vibración cada vez que se repite, porque salta a la de la posición y no interpolar la espalda. CAKeyframeAnimation parece la solución, excepto que yo no puedo conseguir que funcione. Me debe faltar algo. Aquí está mi código mediante el CAKeyframeAnimation (que no funciona):

    NSString *keypath = @"wobbleImage";
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:keypath];
animation.duration = 1.0f;
animation.delegate = self;
animation.repeatCount = 5;

CGFloat wobbleAngle = 0.0872664626f;
NSValue *initial = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(0.0f, 0.0f, 0.0f, 1.0f)];
NSValue *middle = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(wobbleAngle, 0.0f, 0.0f, 1.0f)];
NSValue *final = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(-wobbleAngle, 0.0f, 0.0f, 1.0f)];
animation.values = [NSArray arrayWithObjects:initial, middle, final, nil];

[imageView.layer addAnimation:animation forKey:keypath];


O podría ser una forma totalmente la solución más sencilla que sólo estoy perdiendo. Apreciar los punteros. Gracias!

InformationsquelleAutor jeanniey | 2009-05-30

13 Comentarios

  1. 92

    Forma sencilla de hacerlo:

    #define RADIANS(degrees) (((degrees) * M_PI) / 180.0)
    
    CGAffineTransform leftWobble = CGAffineTransformRotate(CGAffineTransformIdentity, RADIANS(-5.0));
    CGAffineTransform rightWobble = CGAffineTransformRotate(CGAffineTransformIdentity, RADIANS(5.0));
    
    itemView.transform = leftWobble;  //starting point
    
    [UIView beginAnimations:@"wobble" context:itemView];
    [UIView setAnimationRepeatAutoreverses:YES]; //important
    [UIView setAnimationRepeatCount:10];
    [UIView setAnimationDuration:0.25];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(wobbleEnded:finished:context:)];
    
    itemView.transform = rightWobble; //end here & auto-reverse
    
    [UIView commitAnimations];
    
    ...
    
    - (void) wobbleEnded:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context 
    {
         if ([finished boolValue]) {
            UIView* item = (UIView *)context;
            item.transform = CGAffineTransformIdentity;
         }
    }

    Probablemente tiene que jugar con el tiempo y los ángulos, pero esto debe empezar.

    EDIT: he editado la respuesta para agregar el código para poner el artículo en su estado original cuando se hace. También, tenga en cuenta que puede utilizar la beginAnimations valor de contexto para pasar a lo largo de nada para el inicio/parada de métodos. En este caso es el bamboleo objeto de sí mismo, de modo que usted no tiene que confiar en específico ivars y el método puede ser utilizado para cualquier genérico UIView basado objeto (es decir, las etiquetas de texto, imágenes, etc.)

    • esto funciona muy bien, pero la imagen termina en una rotación del estado cuando quiero hasta el final de la espalda en posición vertical en el estado original. ¿sabes que se puede hacer en el bloque de animación de la misma? debo usar el animationDidStopSelector para restablecer la posición?
    • He editado la respuesta a mostrar qué se podía hacer.
    • Bueno! Yo no lo he probado, pero parece como si la vista de inicio abrupto con una girado transformar, y el final abrupto de una girado transformar a la identidad de transformación (= sin rotación). Así que usted necesitará algo más aquí el código para obtener el derecho, es decir, hacer 3 fases. fase 1) gire animado a leftWobble, fase 2) hacer que la animación cosas, fase 3) gire animado de nuevo a la identidad de transformación.
    • Gracias! Esto funciona muy bien para lo que yo necesito. Si va para un suave bamboleo, de las 3 fases mirar como iba a funcionar, aunque tendría sentido utilizar el CAKeyframeAnimation entonces?
    • Sí. Para obtener más complejo multifásico animaciones, especialmente cuando hay falta de tiempo–core animation es más fácil de manejar y de mantener que una cadena de UIView animaciones. UIView es mejor para «fijar y olvidar» tipo de operaciones, especialmente cuando se trata de simple movimiento, escala y rotación. También es útil para cambiar las propiedades de la vista a lo largo del tiempo, como el alfa y los valores de color.
    • Esto fue muy útil. Pregunta: ¿hay una manera de tener esta animación infinitamente HASTA que el usuario pulsa en el vista?
    • Puede establecer el RepeatCount a un número muy alto para que se ejecute durante un largo tiempo. Para obtener un usuario presiona, reemplazar el ‘touchesEnded’ método en el UIView y detener la animación.
    • Wow 🙂 (impresionante) ossam (no se anexa una 🙂 )
    • Sería bueno si el botón podría oscilar a la izquierda y luego a la derecha, por lo que las rocas de un lado a otro en lugar de a la izquierda… sería fácil?

  2. 92

    Ramin que la respuesta fue muy buena, pero desde OS4 el mismo efecto puede lograrse mediante animateWithDuration en una simple función también.

    (adaptado su ejemplo para el futuro de los empleados)

    #define RADIANS(degrees) (((degrees) * M_PI) / 180.0)
    
    - (void)startWobble {
     itemView.transform = CGAffineTransformRotate(CGAffineTransformIdentity, RADIANS(-5));
    
     [UIView animateWithDuration:0.25 
          delay:0.0 
          options:(UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse)
          animations:^ {
           itemView.transform = CGAffineTransformRotate(CGAffineTransformIdentity, RADIANS(5));
          }
          completion:NULL
     ];
    }
    
    - (void)stopWobble {
     [UIView animateWithDuration:0.25
          delay:0.0 
          options:(UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveLinear)
          animations:^ {
           itemView.transform = CGAffineTransformIdentity;
          }
          completion:NULL
      ];
    }
    • Trabajó para mí, como yo estaba buscando un bloque basado en la solución.
    • Creo que esta es la más limpia y sencilla manera de hacerlo, gracias!
    • Funcionó para mí – gracias por la sugerencia.
    • ¿Cómo se puede reiniciar la animación después de cerrar un modal vista?
    • Muy bonito. He encontrado que un ángulo más pequeño (3 o 4 grados) y un corto tiempo de la animación (.1 segundos) más estrechamente la pantalla de inicio animaciones.
  3. 14

    Debe utilizar CAKeyframeAnimation para hacer una suave animación.

    + (void) animationKeyFramed: (CALayer *) layer 
                       delegate: (id) object
                  forKey: (NSString *) key {
    
        CAKeyframeAnimation *animation;
        animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
        animation.duration = 0.4;
        animation.cumulative = YES;
        animation.repeatCount = 2;
        animation.values = [NSArray arrayWithObjects:
                [NSNumber numberWithFloat: 0.0], 
                [NSNumber numberWithFloat: RADIANS(-9.0)], 
                [NSNumber numberWithFloat: 0.0],
                [NSNumber numberWithFloat: RADIANS(9.0)],
                [NSNumber numberWithFloat: 0.0], nil];
        animation.fillMode = kCAFillModeForwards;
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
        animation.removedOnCompletion = NO;
        animation.delegate = object;
    
        [layer addAnimation:animation forKey:key];
    }
  4. 9

    He escrito una aplicación de ejemplo que intenta replicar la pantalla de inicio de bamboleo y el icono de movimiento: iPhone Código de Ejemplo: Azulejos

    • Agradezco que este ejemplo de código también incluye una traducción en la dirección Y; es un efecto sutil que falta de otras soluciones.
    • +1 Gracias por el código y la explicación
    • De gran ayuda. Gracias 🙂
  5. 3

    Para cualquier persona que ha llegado a través de esta publicación, más recientemente, y me gustaría hacer lo mismo en Swift, aquí está mi traducción:

    func smoothJiggle() {
    
        let degrees: CGFloat = 5.0
        let animation = CAKeyframeAnimation(keyPath: "transform.rotation.z")
        animation.duration = 0.6
        animation.cumulative = true
        animation.repeatCount = Float.infinity
        animation.values = [0.0,
            degreesToRadians(-degrees) * 0.25,
            0.0,
            degreesToRadians(degrees) * 0.5,
            0.0,
            degreesToRadians(-degrees),
            0.0,
            degreesToRadians(degrees),
            0.0,
            degreesToRadians(-degrees) * 0.5,
            0.0,
            degreesToRadians(degrees) * 0.25,
            0.0]
        animation.fillMode = kCAFillModeForwards;
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
        animation.removedOnCompletion = true
    
        layer.addAnimation(animation, forKey: "wobble")
    }
    
    func stopJiggling() {
        jiggling = false
        self.layer.removeAllAnimations()
        self.transform = CGAffineTransformIdentity
        self.layer.anchorPoint = CGPointMake(0.5, 0.5)
    }
  6. 2

    La forma más sencilla que conozco es el uso de Core Animation. Básicamente, se crea un Núcleo de Animación Bloque, a continuación, hacer una transformación de rotación y el programa de instalación y el número de repeticiones. Core Animation se encarga entonces de todo lo que se necesita para hacer este efecto de bamboleo.

    Para iniciar un Núcleo de Animación bloque, basta hacer:

    [UIView beginAnimations:@"any string as animationID" context:self];
    [UIView setAnimationRepeatCount:10];
    //rotate 
    [UIView commitAnimations];

    no probado. Pero puede ser que usted también tendrá que hacer:

    [UIView setAnimationBeginsFromCurrentState:YES];

    A. F. R. I. K. setAnimationRepeatCount tendrá el efecto de que la animación se puede hacer, hacer, hacer, hacer, hacer, hacer, hacer… tantas veces como se especifique. Así que usted puede querer primera girar a la izquierda sin número de repetición y, a continuación, a partir de este punto comienzan a tambalearse con el número de repeticiones. Cuando haya terminado, puede que desee girar de nuevo a la identidad de transformación (= no hay rotación y la escala se aplica).

    Puedes cadena de animaciones mediante el establecimiento de la animación delegado con

    [UIView setAnimationDelegate:self]

    y, a continuación,

    [UIView setAnimationDidStopSelector:@selector(myMethod:finished:context:)];

    y tan pronto como la animación se detiene, que el método será llamado. Ver el UIView clase de documentación para saber cómo implementar ese método que será llamado cuando la animación se detiene. Básicamente, dentro de que el método de realizar el siguiente paso (es decir, la rotación de la espalda, o cualquier otra cosa), con una nueva animación de bloque, pero un mismo contexto y animación ID, y luego (si es necesario) especifique otra didStopSelector.

    ACTUALIZACIÓN:

    Es posible que desee comprobar hacia fuera:

    [UIView setAnimationRepeatAutoreverses:YES];

    este se tambalea hacia atrás y adelante automáticamente.

    • gracias, gracias! ¿sólo se puede configurar una cadena de animación bloques, uno tras otro en mi función sin utilizar el DidStopSelector? hay de mejores prácticas o las consideraciones de rendimiento?
    • No. Creo que usted necesita para utilizar el DidStopSelector. Desafortunadamente, la que se ve un poco torpe en el código, ya que usted tendrá que crear un método para cada anymation fase. Pero puede volver a utilizar si la animación se compone de simples fases como «bamboleo» a la izquierda», «bamboleo» de centro» y «bamboleo» a la derecha». Si no te gusta tener más de un método para que, usted puede especificar el método a sí mismo como el didStopSelector. Pero eso sería hacer las cosas más complicadas. Me suelen establecer una serie de métodos, uno después de otro a la cadena de ellos. Y cuando haya terminado, establecer el didStopSelector de nada.
    • Actualizado: Ver [UIView setAnimationRepeatAutoreverses:YES];
    • Tienes razón, la DidStopSelector es necesario. Gracias!
  7. 2

    Puede crear un sinfín de bamboleo efecto mediante la CAKeyframeAnimation, así:

    CGFloat degrees = 8.0;
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
    animation.duration = 0.6;
    animation.cumulative = YES;
    animation.repeatCount = 1;
    animation.values = @[@0.0,
                        @RADIANS(-degrees) * 0.25,
                        @0.0,
                        @RADIANS(degrees) * 0.5,
                        @0.0,
                        @RADIANS(-degrees),
                        @0.0,
                        @RADIANS(degrees),
                        @0.0,
                        @RADIANS(-degrees) * 0.5,
                        @0.0,
                        @RADIANS(degrees) * 0.25,
                        @0.0];
    animation.fillMode = kCAFillModeForwards;
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    animation.removedOnCompletion = YES;
    
    [self.layer addAnimation:animation forKey:@"wobble"];
    • O puede utilizar [animación setRepeatCount:INFINITY];
    • El punto es hacer que no infinito
  8. 1

    Tarde a la fiesta. Estoy usando normalmente la primavera con amortiguación (iOS7 y la más reciente). En swift parece:

        sender.transform = CGAffineTransformMakeScale(1.2, 1.2)
        UIView.animateWithDuration(0.30, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.3, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
            sender.transform = CGAffineTransformMakeScale(1, 1)
        }) { (Bool) -> Void in
        //Do stuff when animation is finished
        }

    Usted puede ajustar el efecto mediante el ajuste de la initialSpringVelocity y SpringWithDamping

    • Este parece que no se mueven (girar).
    • Tiene usted razón! Si desea que gire el uso CGAffineTransformMakeRotation en lugar de CGAffineTransformMakeScale
    • Eso es lo que hice. Gracias por su ayuda!
  9. 1

    Basado en EsbenB la respuesta, pero actualizado para Swift 3 y para la rotación:

        sender.transform = CGAffineTransform(rotationAngle: 12.0 * .pi / 180.0)
        UIView.animate(withDuration: 0.60, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.3, options: .curveEaseInOut, animations: { () -> Void in
            sender.transform = CGAffineTransform(rotationAngle: 0.0)
        }, completion: nil)
  10. 0

    Bien el código dado por Ramin funciona bien.Pero si usted utiliza tabbar aplicación y moverse al siguiente elemento de ficha, a continuación, de nuevo volver a la ficha anterior artículo,usted verá que su punto de vista ha sido movido a la izquierda,cada vez.Así que la mejor práctica es usar ViewWillAppear método.

    - (void)viewWillAppear:(BOOL)animated
    {
        UIView* item = self.view;
        item.transform = CGAffineTransformIdentity;
    }

    por lo que el cada vez que se carga la vista usted encontrará que la animación en el lugar correcto.Y también el uso de este método así.

    [UIView setAnimationDidStopSelector:@selector(myMethod:finished:context:)];
  11. 0

    Swift 3

    func startWiggling() {
        deleteButton.isHidden = false
        guard contentView.layer.animation(forKey: "wiggle") == nil else { return }
        guard contentView.layer.animation(forKey: "bounce") == nil else { return }
    
        let angle = 0.04
    
        let wiggle = CAKeyframeAnimation(keyPath: "transform.rotation.z")
        wiggle.values = [-angle, angle]
        wiggle.autoreverses = true
        wiggle.duration = randomInterval(0.1, variance: 0.025)
        wiggle.repeatCount = Float.infinity
        contentView.layer.add(wiggle, forKey: "wiggle")
    
        let bounce = CAKeyframeAnimation(keyPath: "transform.translation.y")
        bounce.values = [4.0, 0.0]
        bounce.autoreverses = true
        bounce.duration = randomInterval(0.12, variance: 0.025)
        bounce.repeatCount = Float.infinity
        contentView.layer.add(bounce, forKey: "bounce")
    }
    
    func stopWiggling() {
        deleteButton.isHidden = true
        contentView.layer.removeAllAnimations()
    }
  12. 0

    Manera más fácil de este con Swift 4+ :

    static func wobbleAnimation(button: UIButton) {
        CATransaction.begin()
        let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
        rotateAnimation.autoreverses = true
        rotateAnimation.repeatCount = Float.greatestFiniteMagnitude
        rotateAnimation.fromValue = CGFloat(-0.2)
        rotateAnimation.toValue = CGFloat(0.2)
        rotateAnimation.duration = 0.20
        button.layer.add(rotateAnimation, forKey: nil)
        CATransaction.commit()
    }
    
    static func stopWobbleAnimation(button: UIButton) {
        button.layer.removeAllAnimations()
    }
  13. 0

    de respuestas de arriba, tengo la swift5 versión:

      func startWobble() {
        let angle = 5.0 * Double.pi / 180.0;
        self.transform = CGAffineTransform.identity.rotated(by: CGFloat(-angle));
        UIView.animate(withDuration: 0.25, delay: 0, options: [.allowUserInteraction,.repeat,.autoreverse], animations: {
             self.transform = CGAffineTransform.identity.rotated(by: CGFloat(angle));
        }, completion: nil)
    
    }
    
    func stopWobble() {
    
        UIView.animate(withDuration: 0.25, delay: 0, options: [.allowUserInteraction,.beginFromCurrentState,.curveLinear], animations: {
            self.transform = CGAffineTransform.identity;
        }, completion: nil)
    }

Dejar respuesta

Please enter your comment!
Please enter your name here