Disattivazione implicita animazioni in[CALayer setNeedsDisplayInRect:]

Ho una strato con un po complesso il disegno di codice in drawInContext: metodo. Sto cercando di ridurre al minimo la quantità di disegno che devo fare, quindi sto usando -setNeedsDisplayInRect: per aggiornare solo le parti modificate. Questo funziona splendidamente. Tuttavia, quando la grafica di sistema, aggiornamenti del mio livello, è in fase di transizione dalla vecchia alla nuova immagine utilizzando una dissolvenza incrociata. Mi piacerebbe passare istantaneamente.

Ho provato con CATransaction per spegnere le azioni e impostare la durata di zero, e non lavorano. Ecco il codice che sto usando:

[CATransaction begin];
[CATransaction setDisableActions: YES];
[self setNeedsDisplayInRect: rect];
[CATransaction commit];

C’è un metodo diverso su CATransaction dovrei usare invece (ho anche provato setValue:forKey: con kCATransactionDisableActions, con lo stesso risultato).

  • si può fare nel prossimo run loop: dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ });
  • Ho trovato molte risposte al di sotto di lavorare per me. Anche utile è di Apple Modifica di un Livello Predefinito di Comportamento documento, che descrive l’implicita azione della decisione il processo in dettaglio.
  • Questo è un duplicato domanda a questo: stackoverflow.com/a/54656717/5067402

 

15 Replies
  1. 167

    Si può fare impostando il dizionario di azioni sul livello per tornare [NSNull null] come un’animazione per la chiave appropriata. Per esempio, io uso

    NSDictionary *newActions = @{
        @"onOrderIn": [NSNull null],
        @"onOrderOut": [NSNull null],
        @"sublayers": [NSNull null],
        @"contents": [NSNull null],
        @"bounds": [NSNull null]
    };
    
    layer.actions = newActions;

    per disattivare il fade in /out animazioni di inserimento o modifica dei sottolivelli all’interno di uno dei miei livelli, così come i cambiamenti nella dimensione e contenuto del livello. Credo che il contents chiave è quello che stai cercando per prevenire la dissolvenza incrociata sul disegno aggiornato.


    Swift versione:

    let newActions = [
            "onOrderIn": NSNull(),
            "onOrderOut": NSNull(),
            "sublayers": NSNull(),
            "contents": NSNull(),
            "bounds": NSNull(),
        ]
    • Per evitare che il movimento quando si cambia la cornice di utilizzare il @"position" chiave.
    • Anche essere sicuri di aggiungere il @"hidden" proprietà in azione dizionario anche se si sta attivando e disattivando la visibilità di un livello che modo e desidera disattivare l’opacità di animazione.
    • larson – sapete come si desidera utilizzare questa opzione per disabilitare la navigazione animazione nella barra di navigazione (cioè quando un controller di visualizzazione viene inserito nello stack), vale a dire cosa vorresti usare?
    • Che è totalmente estraneo a queste azioni. Questo è solo per Core Animation implicito animazioni sui livelli. Ciò che si descrive è qualcosa di interno di UIKit, quindi non è controllabile via quello che vi mostro qui.
    • Ah Ok grazie per la risposta comunque.
    • c’è un luogo dove tutte queste costanti di stringa sono documentati? Io non riesco a trovare su apple docs
    • Alcuni sono semplicemente animabile proprietà. Più sottile di quelli che può essere scoperto con l’override di -animationForKey: e vedere quali tasti sono animati in risposta a un’azione.
    • che la stessa idea mi è venuta dopo qualche fatica (ho superato actionForKey: invece), alla scoperta di fontSize, contents, onLayout e bounds. Sembra che è possibile specificare un tasto qualsiasi si potrebbe utilizzare in setValue:forKey: metodo, in realtà, specificando complessi percorsi chiave come bounds.size.
    • In realtà, ci sono costanti per questi “speciali” stringhe non rappresenta una proprietà (ad esempio, kCAOnOrderOut per @”onOrderOut”) ben documentato qui: developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/…
    • Non funziona per me. stackoverflow.com/questions/21574661/…
    • incredibilmente utile, grazie mille! La mia soluzione era stata NSStringFromSelector(@selector(contents)), che almeno è alcuni compilare i tempi di controllo. La tua è molto meglio. Potrebbe il Brad aggiornamento la risposta di includere questo? Sono felice di modifica, ma grandi miglioramenti di questo tipo sono spesso respinte in moderazione.
    • guardando a questo, purtroppo, solo tre delle proprietà che sembrano avere i tasti definiti: kCAOnOrderIn, kCAOnOrderOut & kCATransition. Così, Brad probabilmente può lasciare la risposta in quanto è.
    • Solo i tasti che non hanno un corrispondente proprietà sono costanti definite. BTW, il link sembra essere morto, ecco il nuovo URL: developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…
    • Questo non funziona per me. Non possono annullare il mio upvote. Vedere mxcl la risposta qui sotto. Che funziona.
    • Questo è stato l’unico perma soluzione che ha funzionato per me in swift. Ho provato altre soluzioni su questa pagina in molte combinazioni possibili.

  2. 90

    Anche:

    [CATransaction begin];
    [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
    
    //foo
    
    [CATransaction commit];
    • È possibile sostituire //foo con [self setNeedsDisplayInRect: rect]; [self displayIfNeeded]; per rispondere alla domanda originale.
    • Grazie! Questo mi permette di impostare una bandiera animata sul mio custom vista. Pratico da utilizzare all’interno di una tabella cella (cella dove il riutilizzo può portare a trippy animazioni durante lo scorrimento).
    • Porta a problemi di prestazioni per me, l’impostazione di azioni è più performante
    • Abbreviazione: [CATransaction setDisableActions:YES]
    • L’aggiunta di @titaniumdecoy commento, solo nel caso in cui qualcuno ha confuso (come me), [CATransaction setDisableActions:YES] è una scorciatoia per il solo [CATransaction setValue:forKey:] linea. È comunque necessario il begin e commit linee.
  3. 30

    Quando si modifica la proprietà di un layer, CA di solito crea una transazione implicita oggetto da animare il cambiamento. Se non si desidera animare la modifica, è possibile disattivare implicita animazioni con la creazione di una transazione esplicita e impostando la sua kCATransactionDisableActions proprietà vero.

    Objective-C

    [CATransaction begin];
    [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
    //change properties here without animation
    [CATransaction commit];

    Swift

    CATransaction.begin()
    CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
    //change properties here without animation
    CATransaction.commit()
    • setDisableActions: fa lo stesso.
    • Questo è stato il modo più semplice soluzione che ho ottenuto lavorando in Swift!
    • Il commento di @Andy è di gran lunga il migliore e il modo più semplice per fare questo!
  4. 23

    Oltre a Brad Larson risposta: per i livelli personalizzati (che vengono creati da voi) è possibile utilizzare la delega invece di modifica del livello di actions dizionario. Questo approccio è più dinamici e possono essere più performante. E permette di disabilitare tutti implicita animazioni senza dover elencare tutti animabile chiavi.

    Purtroppo, è impossibile utilizzare UIViews personalizzati strato di delegati, in quanto ogni UIView è già un delegato del proprio livello. Ma è possibile utilizzare una semplice classe di supporto come questo:

    @interface MyLayerDelegate : NSObject
        @property (nonatomic, assign) BOOL disableImplicitAnimations;
    @end
    
    @implementation MyLayerDelegate
    
    - (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
    {
        if (self.disableImplicitAnimations)
             return (id)[NSNull null]; //disable all implicit animations
        else return nil; //allow implicit animations
    
        //you can also test specific key names; for example, to disable bounds animation:
        //if ([event isEqualToString:@"bounds"]) return (id)[NSNull null];
    }
    
    @end

    Di utilizzo (all’interno della vista):

    MyLayerDelegate *delegate = [[MyLayerDelegate alloc] init];
    
    //assign to a strong property, because CALayer's "delegate" property is weak
    self.myLayerDelegate = delegate;
    
    self.myLayer = [CALayer layer];
    self.myLayer.delegate = delegate;
    
    //...
    
    self.myLayerDelegate.disableImplicitAnimations = YES;
    self.myLayer.position = (CGPoint){.x = 10, .y = 42}; //will not animate
    
    //...
    
    self.myLayerDelegate.disableImplicitAnimations = NO;
    self.myLayer.position = (CGPoint){.x = 0, .y = 0}; //will animate

    A volte è comodo avere view controller come delegato per la visualizzazione personalizzata dei sottolivelli; in questo caso non c’è bisogno di una classe di supporto, è possibile implementare actionForLayer:forKey: metodo di destra all’interno del controller.

    Nota importante: non cercare di modificare il delegato di UIView‘s livello sottostante (ad esempio, per abilitare implicita animazioni) — cose cattive accadrà 🙂

    Nota: se si desidera animare (non disattivare l’animazione) strato ridisegna, è inutile mettere [CALayer setNeedsDisplayInRect:] chiamata all’interno di un CATransaction, perché reale ridisegnare può (e probabilmente sarà) capita a volte più tardi. Il buon approccio è quello di utilizzare le proprietà personalizzate, come descritto in questa risposta.

    • Questo non funziona per me. Vedi qui.
    • Hmmm. Non ho mai avuto problemi con questo approccio. Il codice in questione legata sembra ok e probabilmente il problema è causato da qualche altro codice.
    • Ah, vedo che hai già risolto, che era sbagliata CALayer che impediva noImplicitAnimations da lavoro. Forse si dovrebbe segnare la tua risposta come corretta e spiegare che cosa era sbagliato con che livello?
    • Mi è stato semplicemente il test sbagliato, CALayer istanza (ho avuto due al momento).
    • Bella soluzione… ma NSNull non implementare il CAAction protocollo e questo non è un protocollo che ha solo metodi opzionali. Questo codice va in crash e non può anche tradurre che a swift. Soluzione migliore: Rendere il vostro oggetto è conforme alla CAAction protocollo (con un vuoto runActionForKey:object:arguments: metodo che non fa nulla) e ritorno self invece di [NSNull null]. Stesso effetto, ma sicuro (non crash di sicuro) e funziona anche in Swift.
    • questo non è corretto. NSNull è consentito in quanto il valore di ritorno. Vedere actionForKey: metodo di riferimento (che CALayerDelegate‘s actionForLayer:forKey: documentazione reindirizza a).
    • In particolare, si dice: “Il delegato deve effettuare una delle seguenti operazioni: 1) Restituire l’oggetto dell’azione per il dato chiave. 2) Restituire la NSNull oggetto se non gestire l’azione.” Inoltre, NSNull non conforme alle CAAction, vedere l’elenco dei protocolli è conforme al riferimento (attualmente, CAAction è il primo protocollo in lista).
    • Il tuo suggerimento di attuazione CAAction da qualche oggetto di controllo di lavoro, anche se, ma non credo sia necessario, anche in Swift. Vi posso assicurare che non ho avuto solo crash causato dal ritorno NSNull da questo metodo. Inoltre, dubito che Apple documentazione e gli esempi potrebbero suggerire qualcosa che poteva causare il crash dell’app.
    • Ah, dimenticavo di aggiungere: per creare NSNull oggetto di Swift, è sufficiente utilizzare NSNull().
    • Mi dispiace, non ho idea di che pagina si sta vedendo, ma il tuo link mi porta a una pagina dove NSNull sicuramente non NON conforme alle CAAction protocollo. Quando cerco di tornare NSNull() in Swift, ho un tempo di compilazione di errore che mi dice esattamente questo. E quando ho letto il delegato documentazione, da nessuna parte dice che si può tornare NSNull. Vedi anche qui s33.postimg.org/si135uetr/… (dove il tuo link mi prende) e qui s33.postimg.org/j5a5ciegv/…
    • Hmm, questo è molto strano. Questo è quello che vedo io: NSNull e actionForKey:. E che cosa stai vedendo quando segui il link per CA guida di programmazione in risposta (“è possibile utilizzare la delega”)? Vedi questo?
    • Grazie per le info, cercherò di utilizzare questo metodo di Swift e di aggiornamento la risposta di conseguenza.
    • NESSUNA fortuna per me in swift con questa risposta. Provato @Mecki suggerimento di come bene. Che cosa ha funzionato, alla fine, è stato la creazione di layer?.actions = ["sublayers":NSNull(),"content":NSNull(),"onOrderOut":NSNull(),"bounds":NSNull(),"hidden":NSNull(),"position":NSNull()]//avoids implicit animation

  5. 8

    Ecco una soluzione più efficiente, simile a accettato risposta, ma per Swift. Per alcuni casi sarà meglio di creazione di una transazione ogni volta che si modifica il valore di una performance preoccupazione come altri hanno detto ad esempio uso comune-caso di trascinare il livello di posizione intorno a 60fps.

    //Disable implicit position animation.
    layer.actions = ["position": NSNull()]      

    Vedere di apple docs per come strato di azioni sono risolti. Attuazione del delegato, saltare un livello di più in cascata, ma nel mio caso che era troppo disordinato a causa della avvertenza circa il delegato bisogno di essere impostato per l’associato UIView.

    Edit: Aggiornamento, grazie per il commentatore sottolineando che NSNull conforme al CAAction.

    • Non c’è bisogno di creare un NullAction per Swift, NSNull è conforme a CAAction già così si può fare la stessa si fa in obiettivo C: layer.azioni = [ “posizione” : NSNull() ]
    • Ho unito la tua risposta con questo per risolvere il mio animazione CATextLayer stackoverflow.com/a/5144221/816017
    • Questo è stato un grande fix per il mio problema di bisogno di ignorare il “animazione” di ritardo quando si modifica il colore di CALayer righe nel mio progetto. Grazie!!
  6. 7

    Basato su Sam risposta, e Simon difficoltà… aggiungere il delegato di riferimento dopo la creazione del CSShapeLayer:

    CAShapeLayer *myLayer = [CAShapeLayer layer];
    myLayer.delegate = self; //<- set delegate here, it's magic.

    … altrove in “m” file…

    Essenzialmente lo stesso come Sam, senza la possibilità di attivare /disattivare tramite il custom “disableImplicitAnimations” variabile disposizione. Più di un “hard-wire” approccio.

    - (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
    
        //disable all implicit animations
        return (id)[NSNull null];
    
        //allow implicit animations
        //return nil;
    
        //you can also test specific key names; for example, to disable bounds animation:
        //if ([event isEqualToString:@"bounds"]) return (id)[NSNull null];
    
    }
  7. 7

    In realtà, non ho trovato alcuna risposta, quella giusta. Il metodo che risolve il problema per me è stato questo:

    - (id<CAAction>)actionForKey:(NSString *)event {   
        return nil;   
    }

    Quindi è possibile qualsiasi logica, per disattivare un’animazione specifica, ma dato che ho voluto rimosso tutti, sono tornato a zero.

  8. 5

    Scoperto un metodo più semplice per disattivare azione all’interno di un CATransaction che chiama internamente setValue:forKey: per il kCATransactionDisableActions chiave:

    [CATransaction setDisableActions:YES];

    Swift:

    CATransaction.setDisableActions(true)
  9. 2

    Aggiungere questo alla tua classe personalizzata in cui si stanno implementando metodo drawRect (). Apportare modifiche al codice alle tue esigenze, per me ‘opacità’ ha fatto il trucco per interrompere il cross-fade animazione.

    -(id<CAAction>) actionForLayer:(CALayer *)layer forKey:(NSString *)key
    {
        NSLog(@"key: %@", key);
        if([key isEqualToString:@"opacity"])
        {
            return (id<CAAction>)[NSNull null];
        }
    
        return [super actionForLayer:layer forKey:key];
    }
  10. 1

    Aggiornato per swift e la disattivazione di un solo implicita di proprietà di animazione in iOS non MacOS

    //Disable the implicit animation for changes to position
    override open class func defaultAction(forKey event: String) -> CAAction? {
        if event == #keyPath(position) {
            return NSNull()
        }
        return super.defaultAction(forKey: event)
    }
    • la soluzione moderna, grazie!
  11. 0

    Di iOS 7 c’è una convenienza metodo che fa proprio questo:

    [UIView performWithoutAnimation:^{
        //apply changes
    }];
    • Non credo che questo metodo di blocchi CALayer animazioni.
    • Ah penso che tu abbia ragione. Non sapeva tanto nel mese di agosto. Devo eliminare questa risposta?
    • 🙂 Io non sono mai sicuro che sia, mi dispiace! I commenti di comunicare l’incertezza ogni caso, quindi è probabilmente bene.
  12. 0

    Per disattivare il fastidioso (sfocata) animazione quando si modifica la proprietà di stringa di un CATextLayer, si può fare questo:

    class CANullAction: CAAction {
        private static let CA_ANIMATION_CONTENTS = "contents"
    
        @objc
        func runActionForKey(event: String, object anObject: AnyObject, arguments dict: [NSObject : AnyObject]?) {
            //Do nothing.
        }
    }

    e quindi utilizzarlo come così (non dimenticare di impostare il CATextLayer correttamente, ad esempio il tipo di carattere corretto, etc.):

    caTextLayer.actions = [CANullAction.CA_ANIMATION_CONTENTS: CANullAction()]

    Si può vedere il mio installazione completa di CATextLayer qui:

    private let systemFont16 = UIFont.systemFontOfSize(16.0)
    
    caTextLayer = CATextLayer()
    caTextLayer.foregroundColor = UIColor.blackColor().CGColor
    caTextLayer.font = CGFontCreateWithFontName(systemFont16.fontName)
    caTextLayer.fontSize = systemFont16.pointSize
    caTextLayer.alignmentMode = kCAAlignmentCenter
    caTextLayer.drawsAsynchronously = false
    caTextLayer.actions = [CANullAction.CA_ANIMATION_CONTENTS: CANullAction()]
    caTextLayer.contentsScale = UIScreen.mainScreen().scale
    caTextLayer.frame = CGRectMake(playbackTimeImage.layer.bounds.origin.x, ((playbackTimeImage.layer.bounds.height - playbackTimeLayer.fontSize) / 2), playbackTimeImage.layer.bounds.width, playbackTimeLayer.fontSize * 1.2)
    
    uiImageTarget.layer.addSublayer(caTextLayer)
    caTextLayer.string = "The text you want to display"

    Ora è possibile aggiornare caTextLayer.stringa di quanto vuoi =)

    Ispirato questo, e questo risposta.

  13. 0

    Provare questo.

    let layer = CALayer()
    layer.delegate = hoo //Same lifecycle UIView instance.

    Avviso

    Se si imposta delegato di UITableView esempio, a volte capita di crash.(Probabilmente scrollview del hittest chiamato ricorsivamente.)

  14. 0

    Se hai bisogno di una molto veloce (ma certamente hacky) fix potrebbe essere la pena solo facendo (Swift):

    let layer = CALayer()
    
    //set other properties
    //...
    
    layer.speed = 999
    • Si prega di non fare questo ffs
    • grazie per questo – si prega di spiegare perché questa è una cattiva idea
    • Perché se uno ha bisogno di spegnere implicita animazioni c’è un meccanismo per farlo (un ca transazione con temporaneamente disabilitato azioni o di impostare in modo esplicito le azioni vuote su un livello). Basta impostare la velocità di animazione per qualcosa, si spera abbastanza alta da far sembrare immediata cause carichi di inutili le prestazioni (che l’autore menziona è rilevante per lui) e il potenziale di varie razze-condizioni (il disegno è ancora fatto in separata buffer di essere animato la visualizzazione in un secondo momento – per essere precisi, per il caso di cui sopra, a 0,25/999 sec).
    • E ‘ davvero un peccato che view.layer?.actions = [:] non funziona davvero. Impostare la velocità è brutto, ma funziona.

Lascia un commento