У меня есть слой со сложным кодом рисования в методе -drawInContext :. Я пытаюсь свести к минимуму объем рисования, который мне нужно сделать, поэтому я использую -setNeedsDisplayInRect: для обновления только измененных частей. Это отлично работает. Однако, когда графическая система обновляет мой слой, он переходит от старого к новому изображению с помощью плавного перехода. Я бы хотел, чтобы он переключился мгновенно.
Я пробовал использовать CATransaction, чтобы отключить действия и установить нулевую продолжительность, но ничего не работает. Вот код, который я использую:
[CATransaction begin];
[CATransaction setDisableActions: YES];
[self setNeedsDisplayInRect: rect];
[CATransaction commit];
Есть ли другой метод для CATransaction, который я должен использовать вместо этого (я также пробовал -setValue: forKey: с kCATransactionDisableActions, тот же результат).
iphone
ios
core-animation
calayer
Бен Готтлиб
источник
источник
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ });
Ответы:
Вы можете сделать это, установив для словаря действий на слое возврат
[NSNull null]
в виде анимации для соответствующего ключа. Например, я используюNSDictionary *newActions = @{ @"onOrderIn": [NSNull null], @"onOrderOut": [NSNull null], @"sublayers": [NSNull null], @"contents": [NSNull null], @"bounds": [NSNull null] }; layer.actions = newActions;
для отключения анимации постепенного появления / исчезновения при вставке или изменении подслоев в одном из моих слоев, а также при изменении размера и содержимого слоя. Я считаю, что
contents
ключ - это тот, который вы ищете, чтобы предотвратить перекрестное затухание на обновленном рисунке.Быстрая версия:
let newActions = [ "onOrderIn": NSNull(), "onOrderOut": NSNull(), "sublayers": NSNull(), "contents": NSNull(), "bounds": NSNull(), ]
источник
@"position"
клавишу.@"hidden"
свойство в словарь действий, если вы переключаете видимость слоя таким образом и хотите отключить анимацию непрозрачности.actionForKey:
вместо этого), обнаруживаяfontSize
,contents
,onLayout
иbounds
. Похоже, вы можете указать любой ключ, который вы могли бы использовать вsetValue:forKey:
методе, фактически указав сложные пути ключей, напримерbounds.size
.Также:
[CATransaction begin]; [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; //foo [CATransaction commit];
источник
//foo
на,[self setNeedsDisplayInRect: rect]; [self displayIfNeeded];
чтобы ответить на исходный вопрос.[CATransaction setDisableActions:YES]
[CATransaction setDisableActions:YES]
является сокращением только для[CATransaction setValue:forKey:]
строки. Вы все еще нужныbegin
иcommit
линия.Когда вы изменяете свойство уровня, CA обычно создает неявный объект транзакции для анимации изменения. Если вы не хотите анимировать изменение, вы можете отключить неявную анимацию, создав явную транзакцию и установив для ее свойства kCATransactionDisableActions значение true .
Цель-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()
источник
В дополнение к ответу Брэда Ларсона : для настраиваемых слоев (которые созданы вами) вы можете использовать делегирование вместо изменения
actions
словаря слоя . Этот подход более динамичен и может быть более производительным. И это позволяет отключить все неявные анимации без необходимости перечислять все анимированные ключи.К сожалению, невозможно использовать
UIView
s в качестве делегатов настраиваемого уровня, потому что каждыйUIView
уже является делегатом своего собственного уровня. Но вы можете использовать такой простой вспомогательный класс:@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
Использование (внутри представления):
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
Иногда удобно иметь контроллер представления в качестве делегата для настраиваемых подслоев представления; в этом случае нет необходимости во вспомогательном классе, вы можете реализовать
actionForLayer:forKey:
метод прямо внутри контроллера.Важное примечание: не пытайтесь изменить делегат
UIView
нижележащего слоя (например, для включения неявной анимации) - будут плохие вещи :)Примечание: если вы хотите анимировать (а не отключать анимацию для) перерисовки слоя, бесполезно помещать
[CALayer setNeedsDisplayInRect:]
вызов внутри aCATransaction
, потому что фактическая перерисовка может (и, вероятно, будет) иногда происходить позже. Хороший подход - использовать настраиваемые свойства, как описано в этом ответе .источник
CALayer
что мешалоnoImplicitAnimations
работать неправильно . Может, стоит отметить свой ответ как правильный и объяснить, что не так с этим слоем?CALayer
экземпляр (у меня тогда было два).NSNull
не реализуетCAAction
протокол, и это не протокол, который имеет только дополнительные методы. Этот код тоже дает сбой, и вы даже не можете перевести это на быстрый. Лучшее решение: сделайте ваш объект совместимым сCAAction
протоколом (с пустымrunActionForKey:object:arguments:
методом, который ничего не делает) и вернитеself
вместо[NSNull null]
. Тот же эффект, но безопасный (точно не выйдет из строя), а также работает в Swift.Вот более эффективное решение, похожее на принятый ответ, но для Swift . В некоторых случаях это будет лучше, чем создавать транзакцию каждый раз, когда вы изменяете значение, что является проблемой производительности, как упоминали другие, например, общий вариант использования перетаскивания позиции слоя со скоростью 60 кадров в секунду.
// Disable implicit position animation. layer.actions = ["position": NSNull()]
См. Документацию Apple, чтобы узнать, как разрешаются действия слоев . Реализация делегата пропустит еще один уровень в каскаде, но в моем случае это было слишком запутанно из-за предостережения о том, что делегат должен быть установлен на связанный UIView .
Изменить: Обновлено благодаря комментатору, указывающему на то, что
NSNull
соответствуетCAAction
.источник
NullAction
для Swift, ужеNSNull
соответствует требованиям,CAAction
поэтому вы можете делать то же самое, что и в цели C: layer.actions = ["position": NSNull ()]На самом деле, я не нашел ни одного правильного ответа. Для меня проблема была решена следующим образом:
- (id<CAAction>)actionForKey:(NSString *)event { return nil; }
Затем вы можете с любой логикой отключить определенную анимацию, но, поскольку я хотел удалить их все, я вернул nil.
источник
CALayer
чтобы переопределить метод.Основываясь на ответе Сэма и трудностях Саймона ... добавьте ссылку на делегата после создания CSShapeLayer:
CAShapeLayer *myLayer = [CAShapeLayer layer]; myLayer.delegate = self; // <- set delegate here, it's magic.
... в другом месте файла "m" ...
По сути, то же самое, что и у Сэма, без возможности переключения с помощью настраиваемого порядка переменных «disableImplicitAnimations». Скорее «жесткий» подход.
- (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]; }
источник
Чтобы отключить неявную анимацию слоев в Swift
CATransaction.setDisableActions(true)
источник
disableActions()
как это звучит так, как будто он делает то же самое, но на самом деле для получения текущего значения. Думаю, он тоже отмечен@discardable
, поэтому его труднее обнаружить. Источник: developer.apple.com/documentation/quartzcore/catransaction/…Обнаруженные более простой способ , чтобы отключить действие внутри ,
CATransaction
что внутренний вызовsetValue:forKey:
дляkCATransactionDisableActions
ключа:[CATransaction setDisableActions:YES];
Swift:
CATransaction.setDisableActions(true)
источник
Добавьте это в свой собственный класс, в котором вы реализуете метод -drawRect (). Внесите изменения в код, чтобы он соответствовал вашим потребностям, для меня «непрозрачность» помогло остановить плавную анимацию.
-(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]; }
источник
Обновлено для быстрого отключения и отключения только одной неявной анимации свойства в iOS, а не в 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) }
Другой пример, в этом случае устранение двух неявных анимаций.
class RepairedGradientLayer: CAGradientLayer { // Totally ELIMINATE idiotic implicit animations, in this example when // we hide or move the gradient layer override open class func defaultAction(forKey event: String) -> CAAction? { if event == #keyPath(position) { return NSNull() } if event == #keyPath(isHidden) { return NSNull() } return super.defaultAction(forKey: event) } }
источник
Если вам когда-нибудь понадобится очень быстрое (но, по общему признанию, хакерское) исправление, возможно, стоит просто сделать (Swift):
let layer = CALayer() // set other properties // ... layer.speed = 999
источник
view.layer?.actions = [:]
это действительно не работает. Установка скорости некрасивая, но работает.Начиная с iOS 7 есть удобный метод, который делает именно это:
[UIView performWithoutAnimation:^{ // apply changes }];
источник
Чтобы отключить раздражающую (размытую) анимацию при изменении строкового свойства CATextLayer, вы можете сделать это:
class CANullAction: CAAction { private static let CA_ANIMATION_CONTENTS = "contents" @objc func runActionForKey(event: String, object anObject: AnyObject, arguments dict: [NSObject : AnyObject]?) { // Do nothing. } }
а затем используйте его так (не забудьте правильно настроить CATextLayer, например, правильный шрифт и т. д.):
caTextLayer.actions = [CANullAction.CA_ANIMATION_CONTENTS: CANullAction()]
Вы можете увидеть мою полную настройку CATextLayer здесь:
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"
Теперь вы можете обновлять caTextLayer.string столько, сколько захотите =)
Вдохновлен этим и этим ответом.
источник
Попробуй это.
let layer = CALayer() layer.delegate = hoo // Same lifecycle UIView instance.
Предупреждение
Если вы установите делегат экземпляра UITableView, иногда происходит сбой (вероятно, проверка результатов scrollview вызывается рекурсивно).
источник