Menonaktifkan animasi tersirat di - [CALayer setNeedsDisplayInRect:]

137

Saya punya layer dengan beberapa kode gambar kompleks di -drawInContext: method. Saya mencoba meminimalkan jumlah gambar yang harus saya lakukan, jadi saya menggunakan -setNeedsDisplayInRect: untuk memperbarui hanya bagian yang diubah. Ini bekerja dengan baik. Namun, ketika sistem grafis memperbarui lapisan saya, itu beralih dari yang lama ke gambar baru menggunakan cross-fade. Saya ingin beralih secara instan.

Saya sudah mencoba menggunakan CATransaction untuk mematikan tindakan dan mengatur durasinya ke nol, dan tidak berhasil. Berikut kode yang saya gunakan:

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

Apakah ada metode berbeda pada CATransaction yang harus saya gunakan sebagai gantinya (Saya juga mencoba -setValue: forKey: dengan kCATransactionDisableActions, hasil yang sama).

Ben Gottlieb
sumber
Anda dapat melakukannya di putaran berikutnya: dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ });
Hashem Aboonajmi
1
Saya menemukan banyak jawaban di bawah ini untuk saya. Juga sangat membantu adalah dokumen Mengubah Perilaku Default Apple , yang menjelaskan proses keputusan tindakan implisit secara terperinci.
ɲeuroburɳ
Ini adalah pertanyaan rangkap untuk yang ini: stackoverflow.com/a/54656717/5067402
Ryan Francesconi

Jawaban:

172

Anda dapat melakukan ini dengan mengatur kamus tindakan pada layer untuk kembali [NSNull null]sebagai animasi untuk tombol yang sesuai. Sebagai contoh, saya menggunakan

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

layer.actions = newActions;

untuk menonaktifkan animasi fade in / out pada penyisipan atau perubahan sublayers dalam salah satu layer saya, serta perubahan ukuran dan isi layer. Saya percaya contentskuncinya adalah yang Anda cari untuk mencegah crossfade pada gambar yang diperbarui.


Versi cepat:

let newActions = [
        "onOrderIn": NSNull(),
        "onOrderOut": NSNull(),
        "sublayers": NSNull(),
        "contents": NSNull(),
        "bounds": NSNull(),
    ]
Brad Larson
sumber
24
Untuk mencegah gerakan saat mengubah bingkai gunakan @"position"tombol.
mxcl
11
Juga pastikan untuk menambahkan @"hidden"properti di kamus tindakan juga jika Anda mengubah visibilitas lapisan seperti itu dan ingin menonaktifkan animasi opacity.
Andrew
1
@BradLarson itu ide yang sama saya datang dengan setelah beberapa berjuang (i mengesampingkan actionForKey:gantinya), menemukan fontSize, contents, onLayoutdan bounds. Sepertinya Anda dapat menentukan kunci apa saja yang dapat Anda gunakan dalam setValue:forKey:metode, sebenarnya menentukan jalur kunci yang kompleks seperti bounds.size.
pqnet
11
Sebenarnya ada konstanta untuk string 'spesial' ini yang tidak mewakili properti (mis. KCAOnOrderOut untuk @ "onOrderOut") didokumentasikan dengan baik di sini: developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/…
Patrick Pijnappel
1
@ Benjohn Hanya kunci yang tidak memiliki properti terkait yang memiliki konstanta yang ditentukan. BTW, tautannya tampaknya sudah mati, inilah URL baru: developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…
Patrick Pijnappel
89

Juga:

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

//foo

[CATransaction commit];
mxcl
sumber
3
Anda dapat mengganti //foodengan [self setNeedsDisplayInRect: rect]; [self displayIfNeeded];untuk menjawab pertanyaan awal.
Karoy Lorentey
1
Terima kasih! Ini memungkinkan saya mengatur bendera animasi pada tampilan kustom saya juga. Berguna untuk digunakan dalam sel tampilan tabel (tempat penggunaan kembali sel dapat menyebabkan beberapa animasi trippy saat menggulir).
Joe D'Andrea
3
Mengarah ke masalah kinerja bagi saya, menetapkan tindakan lebih berkinerja
Pascalius
26
Singkatan:[CATransaction setDisableActions:YES]
titaniumdecoy
7
Menambahkan ke komentar @titaniumdecoy, kalau-kalau ada yang bingung (seperti saya), [CATransaction setDisableActions:YES]adalah singkatan untuk hanya [CATransaction setValue:forKey:]baris. Anda masih membutuhkan begindan commitgaris.
Hlung
31

Saat Anda mengubah properti lapisan, CA biasanya membuat objek transaksi implisit untuk menghidupkan perubahan. Jika Anda tidak ingin menganimasikan perubahan, Anda dapat menonaktifkan animasi tersirat dengan membuat transaksi eksplisit dan mengatur properti kCATransactionDisableActions menjadi true .

Objektif-C

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

Cepat

CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
// change properties here without animation
CATransaction.commit()
pengguna3378170
sumber
6
setDisableActions: melakukan hal yang sama.
Ben Sinclair
3
Yang ini adalah solusi paling sederhana yang saya gunakan di Swift!
Jambaman
Komentar oleh @Andy sejauh ini merupakan cara terbaik dan termudah untuk melakukan ini!
A
23

Selain jawaban Brad Larson : untuk lapisan khusus (yang dibuat oleh Anda), Anda dapat menggunakan delegasi alih-alih memodifikasi actionskamus layer . Pendekatan ini lebih dinamis dan mungkin lebih berkinerja. Dan itu memungkinkan penonaktifan semua animasi implisit tanpa harus mendaftar semua kunci yang dapat dianimasikan.

Sayangnya, tidak mungkin untuk menggunakan UIViews sebagai delegasi lapisan kustom, karena masing UIView- masing sudah merupakan delegasi dari lapisannya sendiri. Tetapi Anda dapat menggunakan kelas pembantu sederhana seperti ini:

@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

Penggunaan (di dalam tampilan):

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

Terkadang, nyaman untuk memiliki pengontrol tampilan sebagai delegasi untuk sublayer khusus tampilan; dalam hal ini tidak perlu kelas pembantu, Anda dapat menerapkan actionForLayer:forKey:metode tepat di dalam controller.

Catatan penting: jangan mencoba memodifikasi delegasi dari UIViewlapisan yang mendasarinya (mis. Untuk mengaktifkan animasi tersirat) - hal-hal buruk akan terjadi :)

Catatan: jika Anda ingin menghidupkan kembali (tidak menonaktifkan animasi untuk) layer redraw, tidak ada gunanya memasukkan [CALayer setNeedsDisplayInRect:]panggilan ke dalam CATransaction, karena redrawing yang sebenarnya mungkin (dan mungkin akan) terjadi kadang-kadang nanti. Pendekatan yang baik adalah dengan menggunakan properti khusus, seperti yang dijelaskan dalam jawaban ini .

skozin
sumber
Ini tidak bekerja untuk saya. Lihat disini.
aleclarson
Hmmm. Saya tidak pernah memiliki masalah dengan pendekatan ini. Kode dalam pertanyaan yang ditautkan terlihat ok dan mungkin masalah ini disebabkan oleh beberapa kode lainnya.
skozin
Ah, saya melihat bahwa Anda sudah beres bahwa itu salah CALayeryang mencegah noImplicitAnimationsbekerja. Mungkin Anda harus menandai jawaban Anda sendiri sebagai benar dan menjelaskan apa yang salah dengan lapisan itu?
skozin
Saya hanya menguji dengan CALayercontoh yang salah (saya punya dua pada saat itu).
aleclarson
1
Solusi yang bagus ... tetapi NSNulltidak menerapkan CAActionprotokol dan ini bukan protokol yang hanya memiliki metode opsional. Kode ini juga macet dan Anda bahkan tidak dapat menerjemahkannya dengan cepat. Solusi yang lebih baik: Buat objek Anda sesuai dengan CAActionprotokol (dengan runActionForKey:object:arguments:metode kosong yang tidak melakukan apa-apa) dan kembali selfsebagai ganti [NSNull null]. Efek yang sama tetapi aman (tidak akan crash pasti) dan juga berfungsi di Swift.
Mecki
9

Inilah solusi yang lebih efisien, mirip dengan jawaban yang diterima tetapi untuk Swift . Untuk beberapa kasus itu akan lebih baik daripada membuat transaksi setiap kali Anda mengubah nilai yang merupakan masalah kinerja seperti yang telah disebutkan orang lain misalnya kasus penggunaan umum menyeret posisi layer sekitar 60fps.

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

Lihat dokumen apple untuk bagaimana tindakan layer diselesaikan . Melaksanakan delegasi akan melewati satu tingkat lagi dalam kaskade tetapi dalam kasus saya yang terlalu berantakan karena peringatan tentang delegasi perlu diatur ke UIView terkait .

Sunting: Diperbarui berkat komentator yang menunjukkan yang NSNullsesuai CAAction.

Jarrod Smith
sumber
Tidak perlu membuat NullActionuntuk Swift, NSNullsesuaikan dengan yang CAActionsudah ada sehingga Anda dapat melakukan hal yang sama dengan yang Anda lakukan pada tujuan C: layer.actions = ["position": NSNull ()]
user5649358
Saya menggabungkan jawaban Anda dengan yang satu ini untuk memperbaiki CATextLayer stackoverflow.com/a/5144221/816017
Erik Zivkovic
Ini adalah perbaikan yang bagus untuk masalah saya yang diperlukan untuk memotong penundaan "animasi" ketika mengubah warna garis CALayer dalam proyek saya. Terima kasih!!
PlateReverb
Pendek dan manis! Solusi bagus!
David H
7

Berdasarkan jawaban Sam, dan kesulitan Simon ... tambahkan referensi delegasi setelah membuat CSShapeLayer:

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

... di tempat lain dalam file "m" ...

Pada dasarnya sama dengan Sam tanpa kemampuan untuk beralih melalui pengaturan variabel "disableImplicitAnimations" kustom. Lebih dari pendekatan "kawat keras".

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

}
bob
sumber
7

Sebenarnya, saya tidak menemukan jawaban yang tepat. Metode yang memecahkan masalah bagi saya adalah ini:

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

Lalu Anda bisa menggunakan logika apa pun di dalamnya, untuk menonaktifkan animasi tertentu, tetapi karena saya ingin menghapus semuanya, saya mengembalikan nihil.

Simon
sumber
5

Untuk menonaktifkan animasi lapisan implisit di Swift

CATransaction.setDisableActions(true)
pawpoise
sumber
Terima kasih atas jawaban ini. Saya pertama kali mencoba menggunakan disableActions()karena kedengarannya seperti melakukan hal yang sama, tetapi sebenarnya untuk mendapatkan nilai saat ini. Saya pikir itu ditandai @discardablejuga, membuat ini lebih sulit dikenali. Sumber: developer.apple.com/documentation/quartzcore/catransaction/…
Austin
5

Menemukan metode sederhana untuk bertindak menonaktifkan di dalam CATransactionyang internal panggilan setValue:forKey:untuk kCATransactionDisableActionskunci:

[CATransaction setDisableActions:YES];

Cepat:

CATransaction.setDisableActions(true)
rounak
sumber
2

Tambahkan ini ke kelas khusus tempat Anda menerapkan metode -drawRect (). Buat perubahan pada kode sesuai kebutuhan Anda, bagi saya 'opacity' melakukan trik untuk menghentikan animasi lintas-pudar.

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

Jika Anda perlu memperbaikinya dengan sangat cepat (tapi harus di-hack) mungkin perlu dilakukan (Swift):

let layer = CALayer()

// set other properties
// ...

layer.speed = 999
Martin CR
sumber
3
Tolong jangan pernah melakukan ini ffs
m1h4
@ m1h4 terima kasih untuk itu - tolong jelaskan mengapa ini adalah ide yang buruk
Martin CR
3
Karena jika seseorang perlu mematikan animasi implisit ada mekanisme untuk melakukan itu (baik transaksi ca dengan tindakan sementara dinonaktifkan atau secara eksplisit menetapkan tindakan kosong ke atas lapisan). Hanya dengan mengatur kecepatan animasi ke sesuatu yang semoga cukup tinggi untuk membuatnya tampak instan menyebabkan banyak overhead kinerja yang tidak perlu (yang menurut penulis asli relevan untuknya) dan potensi untuk berbagai kondisi ras (gambar masih dilakukan menjadi buffer terpisah untuk dianimasikan ke tampilan di lain waktu - tepatnya, untuk kasus Anda di atas, pada 0,25 / 999 detik kemudian).
m1h4
Ini benar-benar memalukan yang view.layer?.actions = [:]tidak benar-benar bekerja. Pengaturan kecepatannya jelek tapi berhasil.
tcurdt
1

Diperbarui untuk cepat dan menonaktifkan hanya satu animasi properti implisit di iOS bukan 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)
}

Contoh lain, dalam hal ini menghilangkan dua animasi implisit.

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)
    }
}
GayleDDS
sumber
0

Pada iOS 7 ada metode kenyamanan yang melakukan hal ini:

[UIView performWithoutAnimation:^{
    // apply changes
}];
Warpling
sumber
1
Saya tidak percaya bahwa metode ini memblokir animasi CALayer .
Benjohn
1
@ Benjohn Ah saya pikir Anda benar. Tidak tahu banyak di bulan Agustus. Haruskah saya menghapus jawaban ini?
Warpling
:-) Saya juga tidak pernah yakin, maaf! Komentarnya mengomunikasikan ketidakpastian, jadi mungkin tidak apa-apa.
Benjohn
0

Untuk menonaktifkan animasi (buram) yang menjengkelkan saat mengubah properti string dari CATextLayer, Anda dapat melakukan ini:

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

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

dan kemudian gunakan seperti itu (jangan lupa mengatur CATextLayer Anda dengan benar, misalnya font yang benar, dll.):

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

Anda dapat melihat pengaturan lengkap saya dari CATextLayer di sini:

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"

Sekarang Anda dapat memperbarui caTextLayer.string sebanyak yang Anda inginkan =)

Terinspirasi oleh ini , dan jawaban ini .

Erik Zivkovic
sumber
0

Coba ini.

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

Peringatan

Jika Anda mengatur delegasi dari UITableView instance, kadang-kadang terjadi crash. (Mungkin scrollview's hittest disebut rekursif.)

Tueno
sumber