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).
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(),
]
Également:
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
//foo
[CATransaction commit];
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()
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 UIView
s 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 .
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
.
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.
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];
}
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)
Pour désactiver les animations de couche implicites dans Swift
CATransaction.setDisableActions(true)
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];
}
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
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 =)
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).
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)
}
Depuis iOS 7 il existe une méthode pratique qui fait exactement cela:
[UIView performWithoutAnimation:^{
// apply changes
}];