2012-02-29 4 views
14

Sto creando una classe di visualizzazione divisa per l'autolayout per una delle mie applicazioni. Tra le sue varie caratteristiche è che può comprimere i riquadri e può animare il loro collasso, proprio come avresti potuto vedere con NSSplitView.NSLayoutConstraint.constant ignora l'animazione

Poiché sto usando vincoli, sto ottenendo questo mettendo un vincolo larghezza richiesta = (larghezza corrente) nel pannello, e quindi impostando il costante 0 vincolo in modo animata:

- (NSLayoutConstraint*)newHiddenConstraintAnimated:(BOOL)animated { 
    NSLayoutConstraint * constraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:NSWidth(self.view.frame)]; 
    constraint.priority = NSLayoutPriorityRequired; 

    CABasicAnimation * anim = [CABasicAnimation animation]; 
    anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; 
    anim.duration = 0.2; 
    constraint.animations = [NSDictionary dictionaryWithObject:anim forKey:@"constant"]; 

    [self.view addConstraint:constraint]; 

    [(animated ? constraint.animator : constraint) setConstant:0.0]; 

    return constraint; 
} 

Funziona magnificamente. Sfortunatamente, espandere il pannello in seguito non va così bene.

- (void)removeHiddenConstraintAnimated:(BOOL)animated { 
    if(!animated) { 
     [self.view removeConstraint:self.hiddenConstraint]; 
    } 
    else { 
     NSLayoutConstraint * constraint = self.hiddenConstraint; 
     NSView * theView = self.view; 

     [NSAnimationContext beginGrouping]; 

     [constraint.animator setConstant:self.width]; 

     [NSAnimationContext currentContext].completionHandler = ^{ 
      [theView removeConstraint:constraint]; 
     }; 

     [NSAnimationContext endGrouping]; 
    } 

    self.hiddenConstraint = nil; 
} 

Se inserisco qualche codice tempi, posso vedere che il gestore di completamento spara quasi istantaneamente, eliminando il vincolo prima che abbia tempo da animare. L'impostazione di una durata su NSAnimationContext non ha alcun effetto.

Qualche idea di cosa potrei fare di sbagliato qui?

+0

Hai mai terminato questa lezione di visualizzazione divisa? Possibilità di andare open source? –

+0

Non ho in programma al momento. È abbastanza specializzato per questa applicazione e credo che 'NSSplitView' in [PURRDACTED] sia stato ridisegnato per funzionare meglio con l'autolayout. –

+0

Oh, capito. Riguardo a [redacted], ha quella caratteristica carina riguardo al layout automatico, ma ovviamente non è retrocompatibile con obiettivi più vecchi. Oh bene, farò il mio credo! :) –

risposta

15

È necessario impostare prima il gestore di completamento e solo allora inviare il messaggio al proxy animatore. Altrimenti, sembra che l'impostazione del gestore di completamento dopo l'avvio dell'animazione venga immediatamente attivata e la costante venga rimossa prima che l'animazione abbia il tempo di terminare. Ho appena controllato questo con un pezzo di codice semplice:

[NSAnimationContext beginGrouping]; 
    NSAnimationContext.currentContext.duration = animagionDuration; 
    NSAnimationContext.currentContext.completionHandler = ^{[self removeConstraint:collapseConstraint];}; 
    [collapseConstraint.animator setConstant:expandedHeight]; 
    [NSAnimationContext endGrouping]; 

Questo funziona perfettamente, ma se si imposta gestore di completamento dopo -setConstant:, l'animazione non avere la possibilità di correre.

+0

Wow, questo ha funzionato davvero. Grazie per il consiglio! –

+0

Niente affatto, qualche tempo fa ho riscontrato lo stesso problema, ed è stata la tua domanda che mi ha portato a sperimentare :) – skh

1

Sto solo alle prese con questa roba me stesso quindi questo potrebbe essere un'analisi ingenua ma:

Mi sembra che si specifica che un'animazione sulle proprietà dei vincoli (nel blocco else) ma, quindi, impostare immediatamente il riferimento al vincolo su zero (potenzialmente rilasciandolo) prima che l'animazione abbia la possibilità di essere eseguito.

Ci si aspetterebbe che si desideri impostare hiddenConstraint su nil dall'interno o attivato dal blocco di completamento dell'animazione.

Nota che se, come è probabile, mi sbaglio Gradirei una parola o due sul perché di aiutarmi a capire meglio :)

+0

È ragionevole presumere che una vista sia proprietaria di eventuali vincoli aggiunti, quindi anche se il vincolo non è referenziato tramite la proprietà 'hiddenConstraint', dovrebbe comunque essere referenziato dall'elenco di vincoli della vista. È anche tenuto in vita dal blocco stesso, poiché i blocchi mantengono i valori di tutte le variabili oggetto che si usano al loro interno. –

+0

Come diceva Peter, 'constraint' è un riferimento forte e viene catturato dal blocco, e anche se non fosse la vista mantiene il vincolo. Ci sono centinaia di vincoli in un auto-caricamento moderatamente complesso, e la stragrande maggioranza è solo referenziata dalle opinioni che li possiedono. –

+0

Grazie per la gentile risposta ragazzi. –

3

Il gestore di completamento è sparare subito perché pensa non ci sono alcuna animazioni che devono essere eseguite. Vorrei controllare e confermare che l'animazione che hai creato sia ancora attaccata alla vista. Per impostazione predefinita, CABasicAnimation è impostato per rimuovere se stesso al completamento tramite la proprietà removedOnCompletion che eredita da CAAnimation (che per impostazione predefinita è impostata su YES).

si vorrà

anim.removedOnCompletion = NO; 
+0

Aggiunta di 'anim.removedOnCompletion = NO;' non ha alcun effetto. Buon suggerimento, però. –

12

Sono d'accordo, questo è abbastanza strano, e potrebbe essere un bug. Lo segnalerei sicuramente perché, per quanto ne so, questo dovrebbe funzionare.

sono stato in grado di farlo funzionare utilizzando il metodo NSAnimationContext classe +runAnimationGroup:completionHandler: al posto delle dichiarazioni beginGrouping e endGrouping:

[NSAnimationContext runAnimationGroup:^(NSAnimationContext* context){ 
    [constraint.animator setConstant:self.width]; 
} completionHandler:^(void){ 
    [theView removeConstraint:constraint]; 
    NSLog(@"completed"); 
}]; 
+0

Purtroppo, questo non sembra aver avuto un effetto sul mio codice. Probabilmente scriverò un radar, però; questo certamente sembra che dovrebbe funzionare. –

+0

È strano. Funziona sicuramente bene qui. –

+0

ha funzionato anche per me. – stevex