Örtük animasyonları devre dışı bırakma - [CALayer setNeedsDisplayInRect:]


137

Onun -drawInContext: yönteminde bazı karmaşık çizim kodu ile bir katman var. Yapmam gereken çizim miktarını en aza indirmeye çalışıyorum, bu yüzden -setNeedsDisplayInRect: sadece değiştirilen parçaları güncellemek için kullanıyorum. Bu harika çalışıyor. Ancak, grafik sistemi katmanımı güncellediğinde, çapraz geçiş kullanarak eskiden yeni görüntüye geçer. Anında geçiş yapmasını istiyorum.

Ben eylemleri kapatmak ve süreyi sıfıra ayarlamak için CATransaction kullanarak denedim ve ne işe yarar. İşte kullanıyorum kodu:

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

Bunun yerine kullanmanız gereken CATransaction üzerinde farklı bir yöntem var mı (-setValue: forKey: kCATransactionDisableActions, aynı sonuç ile de denedim).


bunu bir sonraki çalıştırma döngüsünde yapabilirsiniz: dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ });
Hashem Aboonajmi

1
Aşağıda benim için çalışmak için birçok cevap buldum. Ayrıca , örtük eylem karar sürecini ayrıntılı olarak açıklayan Apple'ın Katmanın Varsayılan Davranışını Değiştirme belgesi de yararlıdır .
ɳeuroburɳ

Bu, bu soruya yinelenen bir soru: stackoverflow.com/a/54656717/5067402
Ryan Francesconi

Yanıtlar:


172

Bunu, katmandaki eylemler sözlüğünü [NSNull null]uygun anahtar için bir animasyon olarak dönecek şekilde ayarlayarak yapabilirsiniz . Örneğin,

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

layer.actions = newActions;

katmanlarımdan birine alt katmanların eklenmesi veya değiştirilmesiyle ilgili solma giriş / çıkış animasyonlarının yanı sıra katmanın boyut ve içeriğindeki değişiklikleri devre dışı bırakmak için. contentsGüncellenmiş çizimde çarpı işareti önlemek için aradığınız anahtar olduğuna inanıyorum .


Hızlı sürüm:

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

24
Çerçeveyi değiştirirken hareketi önlemek için @"position"tuşunu kullanın .
mxcl

11
Ayrıca @"hidden", bir katmanın görünürlüğünü bu şekilde değiştiriyorsanız ve opaklık animasyonunu devre dışı bırakmak istiyorsanız , özelliği eylem sözlüğüne de eklediğinizden emin olun .
Andrew

1
İ (i overrode mücadele sonrası bir ile geldi aynı fikir @BradLarson actionForKey:keşfetmek yerine) fontSize, contents, onLayoutve bounds. setValue:forKey:Yöntemde kullanabileceğiniz herhangi bir anahtarı belirtebiliyorsunuz , aslında gibi karmaşık anahtar yollarını belirtiyorsunuz bounds.size.
pqnet

11
Aslında bir özelliği temsil etmeyen bu 'özel' dizeler için sabitler vardır (örn. KCAOnOrderOut for @ "onOrderOut") burada iyi belgelenmiştir: developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/…
Patrick Pijnappel

1
@Benjohn Yalnızca karşılık gelen bir özelliği olmayan anahtarların sabitleri tanımlanır. BTW, bağlantı
kopmuş

89

Ayrıca:

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

//foo

[CATransaction commit];

3
Orijinal soruyu cevaplamak için //fooile değiştirebilirsiniz [self setNeedsDisplayInRect: rect]; [self displayIfNeeded];.
Karoy Lorentey

1
Teşekkürler! Bu, özel görünümümde de animasyonlu bir bayrak belirlememe izin veriyor. Bir tablo görünümü hücresinde kullanım için kullanışlı (burada hücre yeniden kullanımı kaydırma sırasında bazı trippy animasyonlara yol açabilir).
Joe D'Andrea

3
Benim için performans sorunlarına yol
açıyor

26
[CATransaction setDisableActions:YES]
titaniumdecoy

7
@Titaniumdecoy yorumuna eklemek, herkesin kafam karışması durumunda (benim gibi), [CATransaction setDisableActions:YES]sadece [CATransaction setValue:forKey:]çizgi için bir kısayoldur . Hala beginve commithatlarına ihtiyacınız var .
Hlung

31

Bir katmanın özelliğini değiştirdiğinizde, CA genellikle değişikliği canlandırmak için örtük bir işlem nesnesi oluşturur. Değişikliği canlandırmak istemiyorsanız, açık bir işlem oluşturarak ve kCATransactionDisableActions özelliğini true değerine ayarlayarak örtülü animasyonları devre dışı bırakabilirsiniz .

Objective-C

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

hızlı

CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
// change properties here without animation
CATransaction.commit()

6
setDisableActions: aynısını yapar.
Ben Sinclair

3
Bu, Swift'te çalıştığım en basit çözümdü!
Jambaman

@Andy'nin yorumu, bunu yapmanın en iyi ve en kolay yoludur!
Aᴄʜᴇʀᴏɴғᴀɪʟ

23

Brad Larson'un cevabına ek olarak : (sizin tarafınızdan oluşturulan) özel katmanlar için , katmanın sözlüğünü değiştirmek yerine temsilci kullanabilirsinizactions . Bu yaklaşım daha dinamik ve daha performanslı olabilir. Ayrıca, tüm animasyonlu anahtarları listelemek zorunda kalmadan tüm örtülü animasyonların devre dışı bırakılmasına izin verir.

Ne yazık ki, UIViews'yi özel katman delegeleri olarak kullanmak imkansızdır , çünkü her UIViewbiri zaten kendi katmanının bir temsilcisidir. Ancak bunun gibi basit bir yardımcı sınıf kullanabilirsiniz:

@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

Kullanım (görünümün içinde):

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

Bazen görünümün denetleyicisini, görünümün özel alt katmanları için temsilci olarak kullanmak uygun olur; bu durumda bir yardımcı sınıfa gerek yoktur, actionForLayer:forKey:yöntemi doğrudan kontrolörün içine uygulayabilirsiniz .

Önemli not: UIViewtemel katman temsilcisini değiştirmeye çalışmayın (ör. Örtülü animasyonları etkinleştirmek için) - kötü şeyler olur :)

Not: katman yeniden çizimlerini canlandırmak (animasyonu devre dışı bırakmak istemiyorsanız), a'nın içine [CALayer setNeedsDisplayInRect:]çağrı koymak işe yaramaz CATransaction, çünkü gerçek yeniden çizme bazen daha sonra gerçekleşebilir (ve muhtemelen gerçekleşecektir). İyi yaklaşım, bu cevapta açıklandığı gibi özel özellikler kullanmaktır .


Bu benim için çalışmıyor. Buraya bakın.
aleclarson

Hmmm. Bu yaklaşımla hiç sorun yaşamadım. Bağlantılı sorudaki kod iyi görünüyor ve muhtemelen sorun başka bir koddan kaynaklanıyor.
skozin

Ah, görüyorum ki , çalışmayı CALayerengellenin yanlış olduğunu zaten çözdün noImplicitAnimations. Belki kendi cevabınızı doğru olarak işaretlemeli ve bu katmandaki neyin yanlış olduğunu açıklamalısınız?
skozin

Sadece yanlış CALayerörnekle test yapıyordum (o zaman iki tane vardı).
aleclarson

1
Güzel bir çözüm ... ancak protokolü NSNulluygulamaz CAActionve bu sadece isteğe bağlı yöntemleri olan bir protokol değildir . Bu kod da çöküyor ve bunu hızlı bir şekilde çeviremezsiniz. Daha iyi çözüm: Nesnenizi CAActionprotokole uygun hale getirin ( runActionForKey:object:arguments:hiçbir şey yapmayan boş bir yöntemle) ve selfyerine geri dönün [NSNull null]. Aynı etki ancak güvenli (kesin olarak çökmeyecek) ve Swift'te de çalışıyor.
Mecki

9

İşte kabul edilen cevaba benzer ancak Swift için daha verimli bir çözüm . Bazı durumlarda, bir performans endişesi olan değeri her değiştirdiğinizde bir işlem oluşturmaktan daha iyi olacaktır, çünkü diğerleri bahsettiği gibi, örneğin katman konumunu 60 fps'de sürüklemenin ortak kullanım durumu.

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

Katman işlemlerinin nasıl çözüldüğüne dair apple belgelerine bakın . Temsilcinin uygulanması çağlayanda bir seviye daha atlar, ancak benim durumumda , ilgili UIView'a ayarlanması gereken delege hakkındaki uyarı nedeniyle çok dağınıktı .

Düzenleme: yorumcu olarak güncellendi sayesinde olduğuna işaret ederek NSNulluyan, CAAction.


Gerek bir yaratmak için NullAction, Swift için NSNulluyan, CAAction: layer.actions = [NSNull () "pozisyonu":] Eğer objektif C yapmak aynı şeyi böylece zaten
user5649358


Bu, projemdeki CALayer çizgilerinin rengini değiştirirken "animasyon" gecikmesini atlamak için gereken sorunum için harika bir düzeltmeydi. Teşekkürler!!
PlateReverb

Kısa ve güzel! Harika bir çözüm!
David H

7

Sam'in cevabına ve Simon'un zorluklarına dayanarak ... CSShapeLayer'ı oluşturduktan sonra temsilci referansını ekleyin:

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

... "m" dosyasındaki başka bir yerde ...

Özel olarak "enableImplicitAnimations" değişken düzenlemesi üzerinden geçiş yapma yeteneği olmadan Sam'lerle aynıdır. Daha çok "sert tel" yaklaşımı.

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

Aslında, doğru cevapların hiçbirini bulamadım. Sorunu benim için çözen yöntem şuydu:

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

Sonra belirli bir animasyonu devre dışı bırakmak için ne mantık varsa yapabilirsiniz, ama hepsini kaldırmak istediğimden, nil döndüm.


5

Swift'te örtük katman animasyonlarını devre dışı bırakmak için

CATransaction.setDisableActions(true)

Bu cevap için teşekkürler. İlk disableActions()olarak aynı şeyi yapıyormuş gibi görmeyi denedim , ama aslında mevcut değeri elde etmek. Sanırım bu da işaretlendi @discardable, bu da fark edilmesini zorlaştırdı. Kaynak: developer.apple.com/documentation/quartzcore/catransaction/…
Austin

5

CATransactionDahili setValue:forKey:olarak kCATransactionDisableActionsanahtarı çağıran bir eylemi devre dışı bırakmak için daha basit bir yöntem bulundu :

[CATransaction setDisableActions:YES];

Swift:

CATransaction.setDisableActions(true)

2

Bunu, -drawRect () yöntemini uyguladığınız özel sınıfınıza ekleyin. Kodları ihtiyaçlarınıza uygun şekilde değiştirin, çünkü 'opaklık' çapraz solma animasyonunu durdurmak için hile yaptı.

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

1

Çok hızlı (ama kuşkusuz hacky) bir düzeltmeye ihtiyacınız varsa, sadece (Swift) yapmaya değer olabilir:

let layer = CALayer()

// set other properties
// ...

layer.speed = 999

3
Lütfen asla bu ffs yapmak
m1h4

@ m1h4 bunun için teşekkürler - lütfen bunun neden kötü bir fikir olduğunu açıklayın
Martin CR

3
Çünkü örtük animasyonları kapatmanız gerekiyorsa, bunu yapmak için bir mekanizma vardır (geçici olarak devre dışı bırakılmış eylemleri olan bir ca işlemi veya açıkça bir katmana boş eylemler ayarlamak). Animasyon hızını anında görünmesini umacak kadar yüksek bir şeye ayarlamak, gereksiz performans yükü (orijinal yazarın onun için önemli olduğu) ve çeşitli yarış koşulları potansiyeli (çizim hala ayrı bir tamponda yapılır) daha sonra ekranda göstermek için - yukarıdaki davanız için, 0.25 / 999 saniye sonra kesin).
m1h4

view.layer?.actions = [:]Gerçekten işe yaramayan bir utanç . Hızı ayarlamak çirkin ama işe yarıyor.
tcurdt

1

MacOS'ta değil iOS'ta yalnızca bir örtülü özellik animasyonunu hızlı ve devre dışı bırakmak için güncellendi

// 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)
}

Başka bir örnek, bu durumda iki örtülü animasyonu ortadan kaldırır.

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)
    }
}

0

İtibariyle iOS 7 sadece bunu yapan bir kolaylık yöntem vardır:

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

1
Bu yöntemin CALayer animasyonlarını engellediğine inanmıyorum .
Benjohn

1
@ Benjohn Ah bence haklısın. Ağustos ayında fazla bir şey bilmiyordum. Bu yanıtı silmeli miyim?
Çözgü

:-) Ben de emin değilim, üzgünüm! Yorumlar zaten belirsizliği iletiyor, bu yüzden muhtemelen iyi.
Benjohn

0

CATextLayer öğesinin string özelliğini değiştirirken can sıkıcı (bulanık) animasyonu devre dışı bırakmak için şunları yapabilirsiniz:

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

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

ve sonra böyle kullanın (CATextLayer'ınızı doğru şekilde ayarlamayı unutmayın, örneğin doğru yazı tipi, vb.):

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

CATextLayer kurulumumun tamamını burada görebilirsiniz:

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"

Şimdi caTextLayer.string dosyasını istediğiniz kadar güncelleyebilirsiniz =)

Esinlenen bu ve bu cevap.


0

Bunu dene.

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

Uyarı

UITableView örneğinin temsilcisini ayarlarsanız, bazen kilitlenme meydana gelir. (Muhtemelen scrollview'in hittest'i özyinelemeli olarak adlandırılır.)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.