Отключение неявных анимаций в - [CALayer setNeedsDisplayInRect:]

В методе -drawInContext: у меня есть слой с некоторым сложным кодом чертежа. Я пытаюсь свести к минимуму количество чертежа, которое мне нужно сделать, поэтому я использую -setNeedsDisplayInRect: обновлять только измененные части. Это прекрасно работает. Однако, когда графическая система обновляет мой слой, он переходит от старого к новому изображению с помощью перекрестного затухания. Я бы хотел, чтобы он мгновенно переключился.

Я попытался использовать CATransaction, чтобы отключить действия и установить длительность в ноль, и ни одна из них не работает. Вот код, который я использую:

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

Есть ли другой метод для CATransaction, который я должен использовать вместо этого (я также попытался -setValue: forKey: с kCATransactionDisableActions, тот же результат).

Вы можете сделать это, установив словарь действий на слое так, чтобы он возвращал [NSNull null] в качестве анимации для соответствующего ключа. Например, я использую

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

layer.actions = newActions;

отключить анимацию постепенного появления/вставки при вставке или изменении подслоев в одном из моих слоев, а также изменения размера и содержимого слоя. Я считаю, что ключ contents - это тот, который вы ищете, чтобы предотвратить перекрестное затухание в обновленном чертеже.


Swift версия:

let newActions = [
        "onOrderIn": NSNull(),
        "onOrderOut": NSNull(),
        "sublayers": NSNull(),
        "contents": NSNull(),
        "bounds": NSNull(),
    ]
158
ответ дан 11 февр. '10 в 16:37
источник

также:

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

//foo

[CATransaction commit];
88
ответ дан 30 марта '11 в 19:11
источник

При изменении свойства слоя CA обычно создает неявный объект транзакции для анимации изменения. Если вы не хотите анимировать изменение, вы можете отключить неявные анимации, создав явную транзакцию и установив для свойства kCATransactionDisableActions значение true.

Objective-C

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

Свифта

CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
// change properties here without animation
CATransaction.commit()
28
ответ дан 08 июля '14 в 13:05
источник

В дополнение к ответ Брэда Ларсона: для пользовательских слоев (созданных вами) вы можете использовать делегирование вместо изменения словаря actions. Этот подход более динамичен и может быть более эффективным. И это позволяет отключить все неявные анимации без необходимости перечислять все анимационные клавиши.

К сожалению, невозможно использовать UIView в качестве пользовательских делегатов слоя, потому что каждый 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 "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:] внутри CATransaction, потому что фактическое перерисовка может (и, вероятно, будет) происходить иногда позже. Хороший подход заключается в использовании пользовательских свойств, как описано в этом ответе.

23
ответ дан 19 апр. '13 в 5:32
источник

Здесь более эффективное решение, похожее на принятый ответ, но для Swift. В некоторых случаях это будет лучше, чем создание транзакции каждый раз, когда вы изменяете значение, что является проблемой производительности, как уже упоминалось в других, например, в общем случае перетаскивания позиции слоя со скоростью 60 кадров в секунду.

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

См. Документы Apple, чтобы узнать, как решаются действия слоя. Реализация делегата пропустила бы еще один уровень в каскаде, но в моем случае это было слишком запутанно из-за предостережения о том, что делегату необходимо установить соответствующий ассоциированный UIView.

Редактировать: Обновлено благодаря комментатору, указывающему, что NSNull соответствует CAAction.

8
ответ дан 30 окт. '15 в 2:02
источник

На основе ответа Сэма и трудностей Саймона... добавьте ссылку делегата после создания CSShapeLayer:

CAShapeLayer *myLayer = [CAShapeLayer layer];
myLayer.delegate = self; // <- set delegate here, it 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];

}
7
ответ дан 04 дек. '13 в 0:07
источник

Собственно, я не нашел ни одного ответа, чтобы быть правильным. Метод, который решает проблему для меня, таков:

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

Тогда вы можете использовать любую логику в нем, чтобы отключить определенную анимацию, но так как я хотел удалить их все, я вернул nil.

6
ответ дан 21 мая '13 в 13:48
источник

Обнаружен более простой метод отключения действия внутри CATransaction который внутренне вызывает setValue:forKey: для ключа kCATransactionDisableActions:

[CATransaction setDisableActions:YES];

Swift:

CATransaction.setDisableActions(true)
4
ответ дан 24 марта '15 в 14:10
источник

Чтобы отключить неявную анимацию слоя в Swift

CATransaction.setDisableActions(true)
2
ответ дан 10 апр. '18 в 20:08
источник

Добавьте это в свой собственный класс, где вы используете метод -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];
}
2
ответ дан 14 янв. '15 в 14:53
источник

Как и в iOS 7, существует удобный метод, который выполняет только это:

[UIView performWithoutAnimation:^{
    // apply changes
}];
0
ответ дан 01 авг. '15 в 21:39
источник

Попробуйте это.

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

Внимание

Если вы установите делегат экземпляра UITableView, иногда произойдет сбой. (Вероятно, scrollview hittest называется рекурсивно.)

0
ответ дан 06 апр. '16 в 20:04
источник

Если вам понадобится очень быстрое (но, по общему признанию, хакерское) исправление, возможно, стоит просто сделать (Swift):

let layer = CALayer()

// set other properties
// ...

layer.speed = 999
0
ответ дан 28 мая '16 в 12:12
источник

Чтобы отключить раздражающую (размытую) анимацию при изменении свойства строки 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 столько, сколько хотите =)

Вдохновленный этим и этот ответ.

0
ответ дан 11 марта '16 в 23:28
источник