web-dev-qa-db-fra.com

Désactivation des animations implicites dans - [CALayer setNeedsDisplayInRect:]

J'ai une couche avec un code de dessin complexe dans sa méthode -drawInContext :. J'essaie de minimiser la quantité de dessin que je dois faire, donc j'utilise -setNeedsDisplayInRect: pour mettre à jour uniquement les pièces modifiées. Cela fonctionne à merveille. Cependant, lorsque le système graphique met à jour mon calque, il passe de l'ancienne à la nouvelle image à l'aide d'un fondu enchaîné. Je voudrais qu'il bascule instantanément.

J'ai essayé d'utiliser CATransaction pour désactiver les actions et définir la durée à zéro, et aucun ne fonctionne. Voici le code que j'utilise:

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

Y a-t-il une méthode différente sur CATransaction que je devrais utiliser à la place (j'ai également essayé -setValue: forKey: avec kCATransactionDisableActions, même résultat).

128
Ben Gottlieb

Vous pouvez le faire en définissant le dictionnaire d'actions sur la couche pour renvoyer [NSNull null] comme animation pour la clé appropriée. Par exemple, j'utilise

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

layer.actions = newActions;

pour désactiver les animations de fondu d'entrée/de sortie lors de l'insertion ou du changement de sous-couches dans l'un de mes calques, ainsi que les changements de taille et de contenu du calque. Je crois que la touche contents est celle que vous recherchez afin d'éviter le fondu enchaîné sur le dessin mis à jour.


Version Swift:

let newActions = [
        "onOrderIn": NSNull(),
        "onOrderOut": NSNull(),
        "sublayers": NSNull(),
        "contents": NSNull(),
        "bounds": NSNull(),
    ]
166
Brad Larson

Également:

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];

//foo

[CATransaction commit];
90
mxcl

Lorsque vous modifiez la propriété d'une couche, CA crée généralement un objet de transaction implicite pour animer la modification. Si vous ne souhaitez pas animer la modification, vous pouvez désactiver les animations implicites en créant une transaction explicite et en définissant sa propriété kCATransactionDisableActions sur true.

Objectif 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()
30
user3378170

En plus de réponse de Brad Larson : pour les calques personnalisés (que vous avez créés) vous pouvez utiliser la délégation au lieu de modifier le dictionnaire actions du calque. Cette approche est plus dynamique et peut être plus performante. Et il permet de désactiver toutes les animations implicites sans avoir à répertorier toutes les clés animables.

Malheureusement, il est impossible d'utiliser UIViews comme délégués de couche personnalisés, car chaque UIView est déjà un délégué de sa propre couche. Mais vous pouvez utiliser une classe d'assistance simple comme celle-ci:

@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

Utilisation (à l'intérieur de la vue):

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

Parfois, il est pratique d'avoir le contrôleur de vue en tant que délégué pour les sous-couches personnalisées de vue; dans ce cas, il n'y a pas besoin d'une classe d'assistance, vous pouvez implémenter actionForLayer:forKey: méthode directement dans le contrôleur.

Remarque importante: n'essayez pas de modifier le délégué de la couche sous-jacente de UIView (par exemple pour activer les animations implicites) - de mauvaises choses se produiront :)

Remarque: si vous souhaitez animer (et non désactiver l'animation pour) les calques redessinés, il est inutile de mettre [CALayer setNeedsDisplayInRect:] appel à l'intérieur d'un CATransaction, car le redessin réel peut (et se produira probablement) parfois plus tard. La bonne approche consiste à utiliser des propriétés personnalisées, comme décrit dans cette réponse .

23
skozin

Voici une solution plus efficace, similaire à la réponse acceptée mais pour Swift . Dans certains cas, ce sera mieux que de créer une transaction chaque fois que vous modifiez la valeur, ce qui est un problème de performance comme d'autres l'ont mentionné, par exemple cas d'utilisation courant de déplacement de la position du calque à 60 ips.

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

Voir les documents d'Apple pour comment les actions de calque sont résolues . L'implémentation du délégué sauterait un niveau de plus dans la cascade, mais dans mon cas, c'était trop compliqué en raison de la mise en garde concernant le délégué devant être défini sur l'UIView associé .

Edit: mis à jour grâce au commentateur soulignant que NSNull est conforme à CAAction.

8
Jarrod Smith

En fait, je n'ai trouvé aucune des réponses comme étant la bonne. La méthode qui résout le problème pour moi était la suivante:

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

Ensuite, vous pouvez n'importe quelle logique, pour désactiver une animation spécifique, mais comme je voulais les supprimer toutes, je suis retourné nul.

7
Simon

Sur la base de la réponse de Sam et des difficultés de Simon ... ajoutez la référence du délégué après avoir créé CSShapeLayer:

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

... ailleurs dans le fichier "m" ...

Essentiellement le même que celui de Sam sans la possibilité de basculer via l'arrangement variable personnalisé "disableImplicitAnimations". Plus d'une approche "hard-wire".

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

A découvert une méthode plus simple pour désactiver l'action dans un CATransaction qui appelle en interne setValue:forKey: pour la touche kCATransactionDisableActions:

[CATransaction setDisableActions:YES];

Rapide:

CATransaction.setDisableActions(true)
5
rounak

Pour désactiver les animations de couche implicites dans Swift

CATransaction.setDisableActions(true)
3
pawpoise

Ajoutez ceci à votre classe personnalisée où vous implémentez la méthode -drawRect (). Apportez des modifications au code pour répondre à vos besoins, pour moi, "l'opacité" a fait l'affaire pour arrêter l'animation en fondu enchaîné.

-(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];
}
2
Kamran Khan

Si vous avez besoin d'une solution très rapide (mais certes hacky), cela peut valoir la peine de le faire (Swift):

let layer = CALayer()

// set other properties
// ...

layer.speed = 999
1
Martin CR

Pour désactiver l'animation agaçante (floue) lors de la modification de la propriété de chaîne d'un CATextLayer, vous pouvez procéder comme suit:

class CANullAction: CAAction {
    private static let CA_ANIMATION_CONTENTS = "contents"

    @objc
    func runActionForKey(event: String, object anObject: AnyObject, arguments dict: [NSObject : AnyObject]?) {
        // Do nothing.
    }
}

puis utilisez-le comme cela (n'oubliez pas de configurer correctement votre CATextLayer, par exemple la police correcte, etc.):

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

Vous pouvez voir ma configuration complète de CATextLayer ici:

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"

Vous pouvez maintenant mettre à jour caTextLayer.string autant que vous le souhaitez =)

Inspiré par this , et this answer.

0
Erik Zivkovic

Essaye ça.

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

Avertissement

Si vous définissez le délégué de l'instance UITableView, il peut parfois se produire un crash (probablement le plus important de scrollview appelé de manière récursive).

0
Tueno

Mise à jour pour Swift et désactivation d'une seule animation de propriété implicite dans iOS et 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)
}
0
GayleDDS

Depuis iOS 7 il existe une méthode pratique qui fait exactement cela:

[UIView performWithoutAnimation:^{
    // apply changes
}];
0
Warpling