2012-05-30 11 views
10

Sto tentando di creare uno schermo personalizzato per UIView sullo schermo per 5 secondi quando arriva una notifica remota.Come si fa l'animazione 'seriale' con GCD?

codice come questo:

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

problema e ciò ho bisogno

Ma ci sei casi che un paio di notifica può arrivare in breve intervallo di tempo, in cui diversi customView può animare allo stesso tempo e si può coprire gli altri.

Desidero che queste animazioni vengano eseguite una dopo l'altra, in modo che non siano in conflitto.

Assunta ma non è riuscito

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

Dopo aver effettuato l'animazione nella coda GCD, ho ottenuto lo stesso risultato come il codice di origine che ho usato, che non ha utilizzato GCD. Le animazioni sono ancora in conflitto.

BTW, ho sentito che animazioni o attività che coinvolgono UI devono sempre essere eseguiti su thread principale, ma nel mio secondo codice dell'animazione sembrava fatto frullato. Perché?

risposta

4

Se è la stessa animazione che viene eseguita ogni volta, è possibile memorizzare il numero di volte che l'animazione deve essere eseguita (non la stessa proprietà di ripetizione del conteggio dell'animazione).

Quando si riceve la notifica remota si incrementa il contatore e si chiama il metodo che si anima se il contatore è esattamente uno. Quindi, nel metodoThatAnimates ti ricorsivi in ​​modo ricorsivo nel blocco di completamento mentre diminuisci il contatore ogni volta.Sarebbe simile a questa (con i nomi dei metodi pseudocodice):

- (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

Hai l'idea simile a @Ducan. Grazie per il tuo codice E pensi che dovremmo bloccare 'self.numberOfTimesToRunAnimation'? – studyro

+0

Sì. Non definendo la proprietà come "non anatomica" e non accedendo mai direttamente alla variabile (usando sempre la proprietà) il sistema bloccherà la variabile per te in modo che due thread non leghino/scrivano allo stesso tempo. –

+0

Molto bello. L'ho usato per controllare l'interfaccia utente segmentedControl. Inserendo 'selectedIndex' nella proprietà e impostandolo su' NSNotFound' al completamento, non devo disabilitare il controllo durante l'animazione. Grazie! –

0

Suggerirei di inviare un messaggio nel blocco di completamento a qualsiasi oggetto stia attivando l'animazione. Quindi è possibile che l'oggetto accoda le notifiche stesse e inizi il successivo ogni volta che riceve il messaggio.

1

Si potrebbe utilizzare una (non) concomitante NSOperationQueue per eseguire le animazioni "step by step"

La classe NSOperationQueue regola l'esecuzione di un insieme di oggetti NSOperation. Dopo essere stato aggiunto a una coda, un'operazione rimane in quella coda fino a quando non viene cancellata in modo esplicito o termina l'esecuzione della sua attività. Le operazioni all'interno della coda (ma non ancora in esecuzione) sono esse stesse organizzate in base ai livelli di priorità e alle dipendenze degli oggetti tra operazioni e vengono eseguite di conseguenza. Un'applicazione può creare più code di operazioni e inviare operazioni a qualsiasi di esse.

Le dipendenze tra operazioni forniscono un ordine di esecuzione assoluto per le operazioni , anche se tali operazioni si trovano in code di operazioni diverse. Un oggetto operazione non è considerato pronto per l'esecuzione di finché tutte le sue operazioni dipendenti non hanno completato l'esecuzione. Per le operazioni che sono pronte per l'esecuzione, la coda di operazioni sempre esegue quella con la priorità più alta relativa alle altre operazioni pronte .

+1

animateWithDuration: ... è asincrono. Come risolverebbe il problema? Ogni operazione terminerebbe immediatamente, prima che l'animazione fosse completata. –

+0

imposta la proprietà isFinished nel blocco di completamento dell'animazione. Dalla documentazione: "Il percorso chiave isFinished consente ai client di sapere che un'operazione ha completato correttamente l'attività o è stata annullata e sta per essere interrotta." – CarlJ

4

Utilizzando le code di presentare le animazioni in sequenza non funziona, perché il metodo che inizia l'animazione restituisce immediatamente, e l'animazione viene aggiunto l'animazione albero da eseguire in seguito. Ogni voce nella tua coda verrà completata in una piccola frazione di secondo.

Se ciascuna delle animazioni opera sulla stessa vista, per impostazione predefinita il sistema dovrebbe lasciare che ogni animazione finisca in esecuzione prima che inizi quella successiva.

Per citare la documentazione per il valore opzioni UIViewAnimationOptionBeginFromCurrentState:

UIViewAnimationOptionBeginFromCurrentState

avviare l'animazione dal l'impostazione attuale associata a un'animazione già in volo. Se questa chiave non è presente, tutte le animazioni in volo possono terminare prima dell'avvio della nuova animazione. Se un'altra animazione è non in volo, questa chiave non ha alcun effetto.

Se si vuole catena una serie di animazioni, ecco cosa farei:

creare una serie mutevole di blocchi di animazione. (i blocchi di codice sono oggetti e possono essere aggiunti a un array.) Scrivi un metodo che estrae il blocco dell'animazione superiore dall'array (e lo rimuove dall'array) e lo invia utilizzando animateWithDuration: animazioni: completamento, in cui il metodo di completamento è semplicemente invoca di nuovo il metodo. Fai in modo che il codice assegni un blocco prima di estrarre un elemento dall'array e liberare il blocco dopo aver eliminato l'elemento.

Quindi è possibile scrivere il codice che risponde a una notifica in arrivo affermando il blocco dell'array di animazione, aggiungendo un blocco di animazione al blocco e rilasciando il blocco.

+0

Grazie! Penso che questo sia un modo giusto per fare ciò di cui ho bisogno. – studyro

0

ProcedureKit (basato su NSOperation) è un esempio di soluzione già pronta, ma è piuttosto pesante da utilizzare solo per le animazioni.

mio Operation sottoclasse che uso fare la fila animati pop-up e altre cose:

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() 
    } 
} 

Esempio di utilizzo:

// 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() 
     } 
    } 
} 

In sostanza, è sufficiente aggiungere questa operazione per una coda di serie, e ricorda di chiamare finish() quando finisci di fare il tuo materiale asincrono. Inoltre è possibile annullare tutte le operazioni su una coda seriale con una chiamata e queste verrebbero eliminate con garbo.