Batalkan animasi UIView?

244

Apakah mungkin untuk membatalkan UIViewanimasi saat sedang berlangsung? Atau apakah saya harus turun ke level CA?

yaitu saya telah melakukan sesuatu seperti ini (mungkin juga menetapkan tindakan akhir animasi):

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:duration];
[UIView setAnimationCurve: UIViewAnimationCurveLinear];
// other animation properties

// set view properties

[UIView commitAnimations];

Tetapi sebelum animasi selesai dan saya mendapatkan acara animasi berakhir, saya ingin membatalkannya (potong pendek). Apakah ini mungkin? Googling sekitar menemukan beberapa orang mengajukan pertanyaan yang sama tanpa jawaban - dan satu atau dua orang berspekulasi bahwa itu tidak dapat dilakukan.

philsquared
sumber
Apakah maksud Anda menghentikan animasi di tengah dan membiarkannya di sana? Atau kembali ke tempat semula?
Chris Lundie
2
Entah meninggalkannya persis di tempat itu, pertengahan animasi (dalam praktiknya saya akan memulai animasi lain langsung setelahnya), atau melompat langsung ke titik akhir. Saya membayangkan yang pertama lebih alami, tetapi baik bekerja untuk saya dalam hal ini.
philsquared
3
@ Chris Hanson: Saya menghargai suntingan Anda tetapi bertanya-tanya apa alasan Anda menghapus label iPhone. Mungkin tersirat oleh cocoa-touch, tetapi saya untuk satu memiliki tag filter pada iPhone dan akan melewatkan ini dalam kasus itu.
philsquared
@Boot To The Head (lagi): pertanyaan Anda, dan tanggapan saya sendiri untuk itu, mendorong saya untuk menguji secara terpisah lebih banyak dan saya menemukan jika Anda memulai animasi baru sebelum yang pertama selesai itu tidak melompat ke titik akhir sebelum memulai yang baru.
philsquared
- ini memenuhi kebutuhan langsung saya (dan menyarankan saya memiliki beberapa bug lain dalam kode asli saya), tetapi saya akan membiarkan pertanyaan ini terbuka karena saya tahu orang lain telah meminta hal yang sama.
philsquared

Jawaban:

114

Cara saya melakukannya adalah membuat animasi baru ke titik akhir Anda. Tetapkan durasi yang sangat singkat dan pastikan Anda menggunakan +setAnimationBeginsFromCurrentState:metode ini untuk memulai dari kondisi saat ini. Ketika Anda mengaturnya ke YA, animasi saat ini dipotong pendek. Terlihat seperti ini:

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.1];
[UIView setAnimationCurve: UIViewAnimationCurveLinear];
// other animation properties

// set view properties

[UIView commitAnimations];
Stephen Darlington
sumber
Stephen terima kasih. Sebenarnya jika Anda membaca komentar di bawah pertanyaan saya ini adalah persis apa yang saya lakukan pada akhirnya - tetapi karena Anda telah memberikan akun yang baik, jelas, di sini saya menandainya sebagai diterima
philsquared
1
@ Vojto Bagaimana? Dokumen mengatakan bahwa penggunaan setAnimationBeginsFromCurrentState:tidak disarankan dari iOS 4 tetapi masih ada dan harus tetap berfungsi.
Stephen Darlington
55
di iOS4 +, gunakan opsi UIViewAnimationOptionBeginFromCurrentState on animateWithDuration: delay: options: animations: completion:
Adam Eberbach
Akankah UIViewAnimationOptionBeginFromCurrentState membatalkan animasi yang sedang berjalan atau hanya animasi yang menargetkan properti yang sama? Jika ini adalah animasi, bagaimana Anda bisa membuatnya melanjutkan animasi baru pada titik yang sama TANPA menghentikan animasi yang sama sedang berlangsung?
zakdances
1
@yourfriendzak, berdasarkan tes singkat, sepertinya hanya membatalkan animasi yang menargetkan properti yang sama. Saya mencoba mengubah alpha dari scrollView yang contentOffset dianimasikan dan animasi contentOffset berlanjut.
arlomedia
360

Menggunakan:

#import <QuartzCore/QuartzCore.h>

.......

[myView.layer removeAllAnimations];
Jim Heising
sumber
22
Saya menemukan bahwa saya harus menambahkan #import <QuartzCore / QuartzCore.h> untuk menghapus peringatan kompiler;)
deanWombourne
4
Sangat sederhana, namun sangat sulit ditemukan. Terima kasih untuk ini, saya baru saja menghabiskan satu jam mencoba untuk mencari tahu ini.
MikeyWard
4
Kemungkinan masalah dengan solusi ini adalah bahwa (setidaknya di iOS 5) ada penundaan sebelum mulai berlaku - itu tidak berfungsi segera jika yang Anda maksudkan adalah "hentikan animasinya sekarang, sebelum saya melanjutkan ke baris berikutnya dari kode".
matt
14
Saya menemukan bahwa memanggil ini memicu penyelesaian: ^ (BOOL selesai) blok jika menggunakan + (void) animateWithDuration: (NSTimeInterval) animasi durasi: (void (^) (void)) animasi selesai: (void (^) (BOOL selesai) ) selesai
JWGS
1
@ tikar, apakah Anda tahu solusi yang menghentikan animasi segera? Saya mengamati penundaan kecil juga sebelum animasi benar-benar berhenti.
tiguero
62

Cara termudah untuk menghentikan semua animasi pada tampilan tertentu, segera , adalah ini:

Tautkan proyek ke QuartzCore.framework. Di awal kode Anda:

#import <QuartzCore/QuartzCore.h>

Sekarang, ketika Anda ingin menghentikan semua animasi pada tampilan mati di trek mereka, katakan ini:

[CATransaction begin];
[theView.layer removeAllAnimations];
[CATransaction commit];

Garis tengah akan bekerja dengan sendirinya, tetapi ada penundaan sampai runloop selesai ("redraw moment"). Untuk mencegah penundaan itu, bungkus perintah dalam blok transaksi eksplisit seperti yang ditunjukkan. Ini berfungsi asalkan tidak ada perubahan lain yang dilakukan pada lapisan ini di runloop saat ini.

matt
sumber
2
Saya telah menggunakan [CATransaction flush] setelah kode Anda berfungsi dengan baik, kadang-kadang menyebabkannya tidak segera menulis. Tetapi bagaimanapun, ada masalah kinerja 0,1 detik, misalnya ia tertinggal untuk waktu tertentu. Ada solusi?
Sidwyn Koh
5
removeAllAnimationsakan memiliki efek samping membawa animasi ke bingkai terakhirnya, daripada menghentikannya di tempat sekarang.
Ilya
3
Biasanya, ketika seseorang ingin animasi berhenti, mereka berharap untuk berhenti pada frame saat ini - bukan frame pertama atau frame terakhir. Melompat ke bingkai pertama atau terakhir akan terlihat seperti gerakan tersentak-sentak.
Ilya
1
Saya akan merinci jawabannya kemudian untuk menentukan apa perilaku default dan bagaimana hal itu dapat diubah. Jelas seseorang yang melakukan animasi secara aktif tertarik pada apa yang terjadi di layar setelah pemanggilan metodenya :)
Ilya
1
Saya telah memerinci di tempat lain. Bukan itu yang ditanyakan jadi bukan itu yang saya jawab di sini. Jika Anda memiliki jawaban lain, pasti berikan itu sebagai jawaban!
matt
48

Di iOS 4 dan lebih tinggi, gunakan UIViewAnimationOptionBeginFromCurrentStateopsi pada animasi kedua untuk memotong pendek animasi pertama.

Sebagai contoh, anggap Anda memiliki tampilan dengan indikator aktivitas. Anda ingin memudar dalam indikator aktivitas sementara beberapa aktivitas yang berpotensi memakan waktu dimulai, dan memudar ketika aktivitas selesai. Dalam kode di bawah ini, tampilan dengan indikator aktivitas di dalamnya disebut activityView.

- (void)showActivityIndicator {
    activityView.alpha = 0.0;
    activityView.hidden = NO;
    [UIView animateWithDuration:0.5
                 animations:^(void) {
                     activityView.alpha = 1.0;
                 }];

- (void)hideActivityIndicator {
    [UIView animateWithDuration:0.5
                 delay:0 options:UIViewAnimationOptionBeginFromCurrentState
                 animations:^(void) {
                     activityView.alpha = 0.0;
                 }
                 completion:^(BOOL completed) {
                     if (completed) {
                         activityView.hidden = YES;
                     }
                 }];
}
Ville Laurikari
sumber
41

Untuk membatalkan animasi, Anda hanya perlu mengatur properti yang saat ini sedang animasi, di luar animasi UIView. Itu akan menghentikan animasi di mana pun berada, dan UIView akan melompat ke pengaturan yang baru saja Anda tetapkan.

Tomtaylor
sumber
9
Ini hanya sebagian benar. Itu tidak menghentikan animasi, tetapi itu tidak menghentikannya dari menembakkan metode delegasi, terutama, penangan animasiComplete, jadi jika Anda memiliki logika di sana, Anda akan melihat masalah.
M. Ryan
33
Itulah alasan argumen 'selesai' -animationDidStop:finished:context:. Jika [finished boolValue] == NO, maka Anda tahu bahwa animasi Anda entah bagaimana dibatalkan.
Nick Forge
ini adalah tip yang bagus dari 7 tahun yang lalu! perhatikan ada perilaku aneh: katakanlah Anda menghidupkan alpha kembali dan kedepan, dan Anda membuatnya "pergi ke 0". untuk BERHENTI animasi, Anda tidak dapat mengatur alpha ke nol; Anda mengaturnya dengan mengatakan 1.
Fattie
26

Maaf untuk menghidupkan kembali jawaban ini, tetapi di iOS 10 hal agak berubah, dan sekarang pembatalan dimungkinkan dan Anda bahkan dapat membatalkan dengan anggun!

Setelah iOS 10, Anda dapat membatalkan animasi dengan UIViewPropertyAnimator !

UIViewPropertyAnimator(duration: 2, dampingRatio: 0.4, animations: {
    view.backgroundColor = .blue
})
animator.stopAnimation(true)

Jika Anda lulus benar itu membatalkan animasi dan berhenti tepat di tempat Anda membatalkannya. Metode penyelesaian tidak akan dipanggil. Namun, jika Anda salah, Anda bertanggung jawab untuk menyelesaikan animasi:

animator.finishAnimation(.start)

Anda dapat menyelesaikan animasi dan tetap dalam kondisi saat ini (.current) atau pergi ke status awal (.start) atau ke status akhir (.end)

Omong-omong, Anda bahkan dapat menjeda dan memulai kembali nanti ...

animator.pauseAnimation()
animator.startAnimation()

Catatan: Jika Anda tidak ingin membatalkan secara tiba-tiba, Anda dapat membalikkan animasi atau bahkan mengubah animasi setelah Anda menjeda!

Tiago Almeida
sumber
2
Ini jelas yang paling relevan untuk animasi modern.
Doug
10

Jika Anda menjiwai batasan dengan mengubah konstanta alih-alih properti tampilan, tidak ada metode lain yang berfungsi di iOS 8.

Contoh animasi:

self.constraint.constant = 0;
[self.view updateConstraintsIfNeeded];
[self.view layoutIfNeeded];
[UIView animateWithDuration:1.0f
                      delay:0.0f
                    options:UIViewAnimationOptionCurveLinear
                 animations:^{
                     self.constraint.constant = 1.0f;
                     [self.view layoutIfNeeded];
                 } completion:^(BOOL finished) {

                 }];

Larutan:

Anda harus menghapus animasi dari lapisan tampilan apa pun yang dipengaruhi oleh perubahan kendala dan sublapisannya.

[self.constraintView.layer removeAllAnimations];
for (CALayer *l in self.constraintView.layer.sublayers)
{
    [l removeAllAnimations];
}
Nikola Lajic
sumber
1
Juga hanya self.constraintView.layer.removeAllAnimations()berfungsi (Swift)
Lachtan
Tidak ada solusi lain yang berhasil. Terima kasih, ini berhasil untuk saya.
swapnilagarwal
8

Jika Anda hanya ingin menjeda / menghentikan animasi dengan lancar

self.yourView.layer.speed = 0;

Sumber: Cara menjeda animasi pohon lapisan

Pemenang
sumber
5

Tidak ada satu pun di atas yang menyelesaikannya untuk saya, tetapi ini membantu: UIViewAnimasi mengatur properti segera, lalu menjiwainya. Ini menghentikan animasi ketika lapisan presentasi cocok dengan model (properti yang ditetapkan).

Saya memecahkan masalah saya, yaitu "Saya ingin menghidupkan dari tempat Anda terlihat seperti Anda muncul" ('Anda' artinya melihat). Jika Anda menginginkannya, maka:

  1. Tambahkan QuartzCore.
  2. CALayer * pLayer = theView.layer.presentationLayer;

atur posisi ke lapisan presentasi

Saya menggunakan beberapa opsi termasuk UIViewAnimationOptionOverrideInheritedDuration

Tetapi karena dokumentasi Apple tidak jelas, saya tidak tahu apakah itu benar-benar mengesampingkan animasi lain saat digunakan, atau hanya menyetel ulang pengatur waktu.

[UIView animateWithDuration:blah... 
                    options: UIViewAnimationOptionBeginFromCurrentState ... 
                    animations: ^ {
                                   theView.center = CGPointMake( pLayer.position.x + YOUR_ANIMATION_OFFSET, pLayer.position.y + ANOTHER_ANIMATION_OFFSET);
                   //this only works for translating, but you get the idea if you wanna flip and scale it. 
                   } completion: ^(BOOL complete) {}];

Dan itu harus menjadi solusi yang layak untuk saat ini.

NazCodezz
sumber
5
[UIView setAnimationsEnabled:NO];
// your code here
[UIView setAnimationsEnabled:YES];
ideawu
sumber
2

Versi cepat dari solusi Stephen Darlington

UIView.beginAnimations(nil, context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(0.1)
// other animation properties

// set view properties
UIView.commitAnimations()
lucamegh
sumber
1

Saya memiliki masalah yang sama; API tidak memiliki apa pun untuk membatalkan beberapa animasi tertentu. Itu

+ (void)setAnimationsEnabled:(BOOL)enabled

menonaktifkan SEMUA animasi, dan karenanya tidak berfungsi untuk saya. Ada dua solusi:

1) menjadikan objek animasi Anda subview. Kemudian, ketika Anda ingin membatalkan animasi untuk tampilan itu, hapus tampilan atau sembunyikan. Sangat sederhana, tetapi Anda harus membuat ulang subview tanpa animasi jika Anda perlu melihatnya.

2) ulangi anim hanya satu, dan buat pemilih yang ditunjuk untuk memulai kembali anim jika perlu, seperti ini:

-(void) startAnimation {
NSLog(@"startAnim alpha:%f", self.alpha);
[self setAlpha:1.0];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.0];
[UIView setAnimationRepeatCount:1];
[UIView setAnimationRepeatAutoreverses:YES];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(pulseAnimationDidStop:finished:context:)];
[self setAlpha:0.1];
[UIView commitAnimations];
}

- (void)pulseAnimationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
if(hasFocus) {
    [self startAnimation];
} else {
    self.alpha = 1.0;
}
}

-(void) setHasFocus:(BOOL)_hasFocus {
hasFocus = _hasFocus;
if(hasFocus) {
    [self startAnimation];
}
}

Masalah dengan 2) adalah selalu ada penundaan untuk menghentikan anim saat menyelesaikan siklus animasi saat ini.

Semoga ini membantu.


sumber
1

Bahkan jika Anda membatalkan animasi dengan cara di atas animasi didStopSelectormasih berjalan. Jadi, jika Anda memiliki status logika dalam aplikasi Anda didorong oleh animasi, Anda akan memiliki masalah. Untuk alasan ini dengan cara yang dijelaskan di atas saya menggunakan variabel konteks UIViewanimasi. Jika Anda melewati keadaan saat ini dari program Anda oleh param konteks ke animasi, ketika animasi berhenti didStopSelectorfungsi Anda dapat memutuskan apakah itu harus melakukan sesuatu atau hanya kembali berdasarkan pada keadaan saat ini dan nilai keadaan diteruskan sebagai konteks.

Gorky
sumber
NickForge menjelaskan ini dengan sempurna dalam komentar di bawah ini jawaban yang benar TomTaylor di atas ...
Fattie
Terima kasih banyak atas catatan ini! Saya memang memiliki situasi seperti itu, di mana animasi berikutnya akan diaktifkan ketika animationDidStop dipanggil. Jika Anda cukup menghapus animasi dan segera menambahkan kembali yang baru untuk kunci yang sama saya menemukan itu tidak akan berfungsi. Anda harus menunggu, misalnya hingga pemicu animationDidStop untuk animasi lain, atau yang lainnya. Animasi yang baru saja dihapus tidak akan memunculkan animasiDidStop lagi.
RickJansen
0
CALayer * pLayer = self.layer.presentationLayer;
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView animateWithDuration:0.001 animations:^{
    self.frame = pLayer.frame;
}];
Guntur Kelinci
sumber
0

Untuk menjeda animasi tanpa mengembalikan status ke aslinya atau terakhir:

CFTimeInterval paused_time = [myView.layer convertTime:CACurrentMediaTime() fromLayer:nil];
myView.layer.speed = 0.0;
myView.layer.timeOffset = paused_time;
tommybananas
sumber
0

Ketika saya bekerja dengan UIStackViewanimasi, selain itu removeAllAnimations()saya perlu mengatur beberapa nilai ke yang awal karena removeAllAnimations()dapat mengaturnya ke keadaan tidak terduga. Saya miliki stackViewdengan view1dan view2di dalam, dan satu tampilan harus terlihat dan satu tersembunyi:

public func configureStackView(hideView1: Bool, hideView2: Bool) {
    let oldHideView1 = view1.isHidden
    let oldHideView2 = view2.isHidden
    view1.layer.removeAllAnimations()
    view2.layer.removeAllAnimations()
    view.layer.removeAllAnimations()
    stackView.layer.removeAllAnimations()
    // after stopping animation the values are unpredictable, so set values to old
    view1.isHidden = oldHideView1 //    <- Solution is here
    view2.isHidden = oldHideView2 //    <- Solution is here

    UIView.animate(withDuration: 0.3,
                   delay: 0.0,
                   usingSpringWithDamping: 0.9,
                   initialSpringVelocity: 1,
                   options: [],
                   animations: {
                    view1.isHidden = hideView1
                    view2.isHidden = hideView2
                    stackView.layoutIfNeeded()
    },
                   completion: nil)
}
Paul T.
sumber
0

Tak satu pun dari solusi yang dijawab bekerja untuk saya. Saya memecahkan masalah saya dengan cara ini (saya tidak tahu apakah itu cara yang benar?), Karena saya punya masalah ketika memanggil ini terlalu cepat (ketika animasi sebelumnya belum selesai). Saya melewati animasi yang saya inginkan dengan blok customAnim .

extension UIView
{

    func niceCustomTranstion(
        duration: CGFloat = 0.3,
        options: UIView.AnimationOptions = .transitionCrossDissolve,
        customAnim: @escaping () -> Void
        )
    {
        UIView.transition(
            with: self,
            duration: TimeInterval(duration),
            options: options,
            animations: {
                customAnim()
        },
            completion: { (finished) in
                if !finished
                {
                    // NOTE: This fixes possible flickering ON FAST TAPPINGS
                    // NOTE: This fixes possible flickering ON FAST TAPPINGS
                    // NOTE: This fixes possible flickering ON FAST TAPPINGS
                    self.layer.removeAllAnimations()
                    customAnim()
                }
        })

    }

}
sabiland
sumber