2012-05-30 10 views
10

Estoy tratando de hacer una pantalla personalizada de UIView en pantalla durante 5 segundos cuando se recibe una notificación remota.¿Cómo hacer animaciones "en serie" con GCD?

código como este:

//customView.alpha = 1.0 here 
[UIView animateWithDuration:1 animations:^{ 
            customView.alpha = 0.3; 
           } 
           completion:^(BOOL finished){ 
            // remove customView from super view. 
           }]; 

problema y lo que necesito

Pero hay casos en los que un par de notificación puede llegar en corto intervalo de tiempo, en el que varios customView puede animando al mismo tiempo y uno puede cubrir a los demás.

Quiero que estas animaciones se realicen una tras otra, para que no entren en conflicto.

Asumiendo pero falló

//(dispatch_queue_t)queue was created in other parts of the code 
dispatch_sync(queue, ^{ 
    [UIView animationWithDuration:animations:...]; 
}); 

Después de hacer la animación en la cola GCD, me dieron el mismo resultado que el código de origen utilicé, que no utilizó GCD. Las animaciones siguen siendo conflictivas.

Por cierto, oí que animaciones o tareas que implican la interfaz de usuario siempre se deben ejecutar en el hilo principal, pero en mi segundo código de la animación parecía hecho colado. ¿Por qué?

Respuesta

4

Si se trata de la misma animación que se ejecuta cada vez, entonces podría almacenar el número de veces que se debe ejecutar la animación (no el mismo número de repetición de la animación).

Cuando recibe la notificación remota, incrementa el contador y llama al método que anima si el contador es exactamente uno. Luego, en el método que anima, te llamas recursivamente a ti mismo en el bloque de finalización mientras reduces el contador cada vez.Se vería algo como esto (con nombres de métodos pseudocódigo):

- (void)methodThatIsRunWhenTheNotificationIsReceived { 
    // Do other stuff here I assume... 
    self.numberOfTimesToRunAnimation = self.numberOfTimesToRunAnimation + 1; 
    if ([self.numberOfTimesToRunAnimation == 1]) { 
     [self methodThatAnimates]; 
    } 
} 

- (void)methodThatAnimates { 
    if (self.numberOfTimesToRunAnimation > 0) { 
     // Animation preparations ... 
     [UIView animateWithDuration:1 
         animations:^{ 
            customView.alpha = 0.3; 
         } 
         completion:^(BOOL finished){ 
            // Animation clean up ... 
            self.numberOfTimesToRunAnimation = self.numberOfTimesToRunAnimation - 1; 
            [self methodThatAnimates]; 
         }]; 
    } 
} 
+1

Tiene la idea similar a @Ducan. Gracias por tu codigo ¿Y crees que deberíamos bloquear 'self.numberOfTimesToRunAnimation'? – studyro

+0

Sí. Al no definir la propiedad como "no atómica" y nunca acceder directamente a la variable (siempre utilizando la propiedad) el sistema bloqueará la variable para que dos hilos no lean/escriban al mismo tiempo. –

+0

Muy agradable. Lo usé para controlar la IU segmentada de control. Al poner el 'selectedIndex' en la propiedad y establecerlo en' NSNotFound' al finalizar, no es necesario que deshabilite el control durante la animación. ¡Gracias! –

0

Sugeriría que se envíe un mensaje en el bloque de finalización al objeto que está activando la animación. Entonces podría hacer que ese objeto ponga en cola las notificaciones y comience el próximo cada vez que reciba el mensaje.

1

Se puede usar un (no) concurrente NSOperationQueue para realizar las animaciones "paso a paso"

La clase NSOperationQueue regula la ejecución de un conjunto de objetos NSOperation. Después de agregarse a una cola, una operación permanece en esa cola hasta que se cancela explícitamente o termina de ejecutar su tarea. Las operaciones dentro de la cola (pero aún no se están ejecutando) están organizadas de acuerdo con niveles de prioridad y dependencias de objetos entre operaciones y se ejecutan en consecuencia. Una aplicación puede crear múltiples colas de operaciones y enviar operaciones a cualquiera de ellas.

Las dependencias entre operaciones proporcionan un orden de ejecución absoluto para las operaciones , incluso si esas operaciones se encuentran en colas de operación diferentes. Un objeto de operación no se considera listo para ejecutar hasta que todas sus operaciones dependientes hayan terminado de ejecutarse. Para las operaciones que están listas para ejecutarse, la cola de operaciones siempre ejecuta la que tiene la prioridad más alta relativa a las otras operaciones listas para .

+1

animateWithDuration: ... es asincrónico. ¿Cómo resolvería eso el problema? Cada operación terminaría inmediatamente, antes de que la animación se complete. –

+0

establece la propiedad isFinished en el bloque de finalización de la animación. De los documentos: "La ruta clave isFinished les permite a los clientes saber que una operación finalizó su tarea con éxito o que se canceló y se está cerrando". – CarlJ

4

El uso de colas para presentar animaciones en secuencia no va a funcionar, porque el método que comienza la animación se vuelve inmediatamente, y la animación se añade a la animación árbol que se realizará más tarde. Cada entrada en tu cola se completará en una pequeña fracción de segundo.

Si cada una de tus animaciones funciona en la misma vista, de manera predeterminada, el sistema debería permitir que cada animación termine de ejecutarse antes de que comience la siguiente.

Para citar los documentos para el valor opciones UIViewAnimationOptionBeginFromCurrentState:

UIViewAnimationOptionBeginFromCurrentState

iniciar la animación de la configuración actual asociado con una animación ya en vuelo. Si esta clave no está presente, se permite que las animaciones en vuelo finalicen antes de que se inicie la nueva animación. Si otra animación es no en vuelo, esta tecla no tiene ningún efecto.

Si desea encadenar una serie de animaciones, esto es lo que yo haría:

crear una matriz mutable de bloques de animación. (los bloques de código son objetos y se pueden agregar a una matriz). Escriba un método que extraiga el bloque de animación superior de la matriz (y lo elimine de la matriz) y lo envíe usando animateWithDuration: animations: completion, donde el método de finalización simplemente invoca el método de nuevo. Haga que el código afirme un bloqueo antes de sacar un elemento del conjunto y libere el bloqueo después de eliminar el elemento.

Luego puede escribir un código que responda a una notificación entrante al afirmar su bloqueo de matriz de animación, agregar un bloque de animación al bloqueo y soltar el bloqueo.

+0

¡Gracias! Creo que esta es una forma correcta de hacer lo que necesito. – studyro

0

ProcedureKit (basado en NSOperation) es un ejemplo de solución preparada, pero es bastante pesado usarlo solo para animaciones.

Mi Operation subclase que utilizo para hacer cola animados pop-ups y otras cosas:

class SerialAsyncOperation: Operation { 

    private var _started = false 

    private var _finished = false { 
     willSet { 
      guard _started, newValue != _finished else { 
       return 
      } 
      willChangeValue(forKey: "isFinished") 
     } 
     didSet { 
      guard _started, oldValue != _finished else { 
       return 
      } 
      didChangeValue(forKey: "isFinished") 
     } 
    } 

    private var _executing = false { 
     willSet { 
      guard newValue != _executing else { 
       return 
      } 
      willChangeValue(forKey: "isExecuting") 
     } 
     didSet { 
      guard oldValue != _executing else { 
       return 
      } 
      didChangeValue(forKey: "isExecuting") 
     } 
    } 

    override var isAsynchronous: Bool { 
     return true 
    } 

    override var isFinished: Bool { 
     return _finished 
    } 

    override var isExecuting: Bool { 
     return _executing 
    } 

    override func start() { 
     guard !isCancelled else { 
      return 
     } 
     _executing = true 
     _started = true 
     main() 
    } 

    func finish() { 
     _executing = false 
     _finished = true 
    } 

    override func cancel() { 
     _executing = false 
     _finished = true 
     super.cancel() 
    } 
} 

Ejemplo de uso:

// Setup a serial queue 
private lazy var serialQueue: OperationQueue = { 
    let queue = OperationQueue() 
    queue.maxConcurrentOperationCount = 1 
    queue.name = String(describing: type(of: self)) 
    return queue 
}() 

// subclass SerialAsyncOperation 
private class MessageOperation: SerialAsyncOperation { 

    // ... 

    override func main() { 
     DispatchQueue.main.async { [weak self] in 
      // do UI stuff 

      self?.present(completion: { 
       self?.finish() 
      }) 
     } 
    } 

    func present(completion: @escaping() -> Void) { 
     // do async animated presentation, calling completion() in its completion 
    } 

    func dismiss(completion: @escaping() -> Void) { 
     // do async animated dismissal, calling completion() in its completion 
    } 

    // animated cancellation support 
    override func cancel() { 
     if isExecuting { 
      dismiss(completion: { 
       super.cancel() 
      }) 
     } else { 
      super.cancel() 
     } 
    } 
} 

Básicamente, sólo tiene que añadir esta operación a una cola de serie y recuerde llamar al finish() cuando termine de hacer sus cosas asincrónicas. También puede cancelar todas las operaciones en una cola serie con una llamada, y estas se descartarán correctamente.

Cuestiones relacionadas