Скасувати анімацію UIView?


244

Чи можливо скасувати UIViewанімацію, поки вона працює? Або мені доведеться опуститися до рівня СА?

тобто я зробив щось подібне (можливо, також встановив кінцеву анімаційну дію):

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

// set view properties

[UIView commitAnimations];

Але перш ніж анімація завершиться, і я отримаю подію, яка закінчила анімацію, я хочу скасувати її (скоротити її). Чи можливо це? Погулявши навколо, знайдеться кілька людей, які задають одне і те ж питання, без відповідей - і один або два люди припускають, що цього не можна зробити.


Ви маєте на увазі призупинити анімацію посередині і залишити її там? Або повертатися туди, де було на старті?
Кріс Лунді

2
Або залишити його саме там, де він є, середньої анімації (на практиці я запускаю іншу анімацію відразу після цього), або переходьте прямо до кінцевої точки. Я думаю, що перший є більш природним, але в цьому випадку для мене це працює.
фільтрований

3
@Chris Hanson: Я ціную ваші зміни, але цікаво, що ви обґрунтовуєте для видалення тегу iPhone. Це може мати на увазі какао-дотик, але я для одного маю фільтр тегів на iPhone і пропустив би це у такому випадку.
фільтрований

@Boot To The Head (знову ж таки): ваше запитання та моя власна відповідь на нього запропонували мені ще трохи перевірити ізоляцію, і я виявив, якщо ви запускаєте нову анімацію до того, як закінчиться перша, вона переходить до кінцевої точки перед початком нового.
фільтрований

- це відповідає моїм негайним потребам (і дозволяє припустити, що в моєму реальному коді є якась інша помилка), але я збираюся залишити це питання відкритим, оскільки знаю, що інші просять те саме.
фільтрований

Відповіді:


114

Як я це роблю, це створити нову анімацію до своєї кінцевої точки. Встановіть дуже коротку тривалість і переконайтесь, що ви використовуєте +setAnimationBeginsFromCurrentState:метод для початку з поточного стану. Коли ви встановите його на ТАК, поточна анімація буде скорочена. Виглядає приблизно так:

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

// set view properties

[UIView commitAnimations];

Дякую Стівен. Насправді, якщо ви читаєте коментарі під моїм запитанням, це саме те, що я зробив, врешті-решт, але, оскільки ви дали хороший, зрозумілий, звіт про це тут, я відзначаю це як прийняте
фільтр

1
@Vojto Як так? Документи говорять, що використання setAnimationBeginsFromCurrentState:iOS 4 відмовляється від iOS 4, але він все ще існує і повинен функціонувати.
Стівен Дарлінгтон

55
в iOS4 + використовуйте параметр UIViewAnimationOptionBeginFromCurrentState в animateWithDuration: затримка: параметри: анімація: завершення:
Адам Ебербах

Чи скасує UIViewAnimationOptionBeginFromCurrentState будь-яка анімація, яка виконується, або лише анімацію, націлену на ту саму властивість? Якщо це будь-яка анімація, як ви можете змусити її продовжувати нову анімацію в той же момент БЕЗ зупинки тієї самої анімації, яка виконується?
zakdances

1
На основі короткого тесту @yourfriendzak, схоже, це лише скасовує анімацію, націлену на те саме властивість. Я спробував змінити альфа scrollView, вміст якого був анімований, а анімація contentOffset тривала.
arlomedia

360

Використання:

#import <QuartzCore/QuartzCore.h>

.......

[myView.layer removeAllAnimations];

22
Я виявив, що мені потрібно додати #import <QuartzCore / QuartzCore.h>, щоб видалити попередження компілятора;)
deanWombourne

4
Настільки просте, але все ж таке важко знайти. Дякую за це, я щойно витратив годину, намагаючись розібратися в цьому.
MikeyWard

4
Можлива проблема цього рішення полягає в тому, що (як мінімум, в iOS 5) є затримка, перш ніж вона набуде чинності - вона не працює досить швидко, якщо ви маєте на увазі «зупиніть анімацію прямо зараз, перш ніж перейти до наступного рядка код ".
мат

14
Я виявив, що виклик цього ініціює завершення: блоку ^ (закінчено BOOL), якщо використовується анімація тривалості + (void) animateWithDuration: (NSTimeInterval): (void (^) (void)) завершення анімації: (void (^) (BOOL завершено) ) завершення
JWGS

1
@matt Ви знаєте рішення, яке негайно зупинить анімацію? Я також спостерігаю невелику затримку до того, як анімація дійсно зупиниться.
tiguero

62

Найпростіше способу зупинити всі анімації на певну точку зору, відразу ж , полягає в наступному:

Пов’яжіть проект на QuartzCore.framework. На початку коду:

#import <QuartzCore/QuartzCore.h>

Тепер, коли ви хочете зупинити всі анімації на погляді мертвим у своїх записах, скажіть це:

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

Середня лінія працювала б сама по собі, але є затримка до завершення запуску циклу ("момент перемальовки"). Щоб запобігти цій затримці, загорніть команду в явний блок транзакцій, як показано. Це працює за умови, що на цьому шарі в поточному циклі не було здійснено інших змін.


2
Я використовую [CATransaction flush] після того, як ваш код працює чудово, іноді тому, що він записується не відразу. Однак є питання про продуктивність за 0,1 секунди, наприклад, це відстає на конкретний час. Будь-які обходи?
Сідвін Кох

5
removeAllAnimationsматиме побічний ефект від приведення анімації до її остаточного кадру, а не зупинки її там, де зараз є.
Ілля

3
Зазвичай, коли хочеться, щоб анімація зупинилася, вони очікують, що вона зупиниться на її поточному кадрі - не на першому кадрі, ні на останньому кадрі. Стрибки на перший або останній кадр будуть виглядати як ривковий рух.
Ілля

1
Потім я детально відповім, щоб вказати, що таке поведінка за замовчуванням і як це можна змінити. Очевидно, хтось, хто займається анімацією, активно цікавиться тим, що відбувається на екрані за його дзвінками :)
Ілля,

1
Я це детально описав деінде. Це не те, про що питали, тому це не те, на що я відповів тут. Якщо у вас є інша відповідь, обов'язково дайте це як відповідь!
мат

48

На iOS 4 і новіших версіях використовуйте UIViewAnimationOptionBeginFromCurrentStateпараметр другої анімації, щоб скоротити першу анімацію.

Наприклад, припустімо, що у вас є подання з індикатором активності. Ви хочете згасати в індикаторі активності, поки починається деяка потенційно трудомістка діяльність, і згасати її, коли діяльність закінчується. У наведеному нижче коді виклик подання з покажчиком активності 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;
                     }
                 }];
}

41

Щоб скасувати анімацію, вам просто потрібно встановити властивість, яка наразі анімована, поза анімацією UIView. Це зупинить анімацію, де б вона не була, і UIView перейде до встановленого вами налаштування.


9
Це лише частково правда. Це зупиняє анімацію, але це не заважає запускати делегатні методи, особливо, обробник animationComplete, тому якщо у вас є логіка, ви побачите проблеми.
М. Райан

33
Саме для цього і є "готовий" аргумент -animationDidStop:finished:context:. Якщо [finished boolValue] == NO, то ви знаєте, що вашу анімацію якось скасували.
Нік Форж

це чудова порада від 7 років тому! Зауважте, є дивна поведінка: скажіть, що ви анімуєте альфа назад і вперед, і ви змушуєте "переходити до 0". щоб зупинити анімацію, ви не можете встановити альфа до нуля; ви встановили це так, щоб сказати 1.
Fattie

26

Вибачте, що цю відповідь виправдаю, але в iOS 10 щось змінилося, і тепер скасування можливе, і ви навіть можете граціозно скасувати!

Після iOS 10 ви можете скасувати анімацію за допомогою UIViewPropertyAnimator !

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

Якщо ви перейдете на істину, вона скасовує анімацію, і вона зупиняється прямо там, де ви її скасували. Метод завершення не буде називатися. Однак якщо ви передаєте помилку, ви несете відповідальність за закінчення анімації:

animator.finishAnimation(.start)

Ви можете закінчити свою анімацію і залишитися в поточному стані (.current) або перейти до початкового стану (.start) або до кінця (.end)

До речі, ви навіть можете призупинити і перезапустити пізніше ...

animator.pauseAnimation()
animator.startAnimation()

Примітка. Якщо ви не хочете різкого скасування, ви можете скасувати анімацію або навіть змінити анімацію після призупинення!


2
Це, безумовно, найбільш актуально для сучасної анімації.
Дуг

10

Якщо ви анімуєте обмеження, змінюючи константу замість властивості перегляду, жоден з інших методів не працює на iOS 8.

Приклад анімації:

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

                 }];

Рішення:

Вам потрібно видалити анімацію з шарів будь-яких поглядів, на які впливає зміна обмеження та їх підшарів.

[self.constraintView.layer removeAllAnimations];
for (CALayer *l in self.constraintView.layer.sublayers)
{
    [l removeAllAnimations];
}

1
Також self.constraintView.layer.removeAllAnimations()працює лише (Свіфт)
Lachtan

Жодне з інших рішень не працювало. Дякую, воно працювало на мене.
swapnilagarwal


5

Ніщо з вищезазначеного не вирішило це для мене, але це допомогло: UIViewАнімація встановлює властивість негайно, а потім анімує її. Він зупиняє анімацію, коли рівень презентації відповідає моделі (властивість заданого).

Я вирішив своє питання, яке було "Я хочу анімувати там, де ти схожий на те, що ти з'явився" ("ти" означає перегляд). Якщо ви хочете ТО, то:

  1. Додати QuartzCore.
  2. CALayer * pLayer = theView.layer.presentationLayer;

встановити позицію на презентаційний шар

Я використовую кілька варіантів, в тому числі UIViewAnimationOptionOverrideInheritedDuration

Але оскільки документація Apple нечітка, я не знаю, чи вона дійсно перекриває інші анімації при використанні, або просто скидає таймери.

[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) {}];

І це зараз має бути гідним рішенням.


5
[UIView setAnimationsEnabled:NO];
// your code here
[UIView setAnimationsEnabled:YES];

2

Швидка версія рішення Стівена Дарлінгтона

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

// set view properties
UIView.commitAnimations()

1

У мене така ж проблема; в API немає нічого, щоб скасувати певну анімацію. The

+ (void)setAnimationsEnabled:(BOOL)enabled

вимикає ВСІ анімації, і, отже, не працює для мене. Є два рішення:

1) зробити свій анімований об’єкт підпереглядом. Потім, коли ви хочете скасувати анімацію для цього перегляду, видаліть його або прихойте. Дуже просто, але вам потрібно відтворити підвид без анімації, якщо вам потрібно тримати його у перегляді.

2) повторіть лише анімацію та зробіть перемикач делегата, щоб перезапустити анімацію, якщо потрібно:

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

Проблема 2) полягає в тому, що аніму завжди затримується, коли вона закінчує поточний цикл анімації.

Сподіваюсь, це допомагає.


1

Навіть якщо ви скасуєте анімацію способами, описаними вище, анімація didStopSelectorвсе ще працює. Отже, якщо у вашій програмі є логічні стани, керовані анімацією, у вас виникнуть проблеми. З цієї причини, описаними вище способами я використовую контекстну змінну UIViewанімацій. Якщо ви передаєте поточний стан вашої програми контекстним парам до анімації, коли анімація припиняє вашу didStopSelectorфункцію, ви можете вирішити, чи слід щось робити чи просто повертатися на основі поточного стану та значення стану, переданого як контекст.


NickForge чудово пояснює це в коментарі нижче правильної відповіді TomTaylor вище ...
Fattie

Дякую за цю замітку! У мене справді є така ситуація, коли наступна анімація запускається, коли викликається animationDidStop. Якщо ви просто видалите анімацію і негайно додасте нову для того ж ключа, я вважаю, що це не спрацює. Вам потрібно зачекати, наприклад, до запуску animationDidStop для іншої анімації чи чогось іншого. Щойно видалена анімація більше не запускає animationDidStop.
RickJansen

0
CALayer * pLayer = self.layer.presentationLayer;
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView animateWithDuration:0.001 animations:^{
    self.frame = pLayer.frame;
}];

0

Щоб призупинити анімацію, не повертаючи стан до початкового чи остаточного:

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

0

Коли я працюю з UIStackViewанімацією, окрім того, removeAllAnimations()мені потрібно встановити початкові значення, оскільки вони removeAllAnimations()можуть встановити їх у непередбачуваний стан. У мене є stackViewз view1і view2всередині, і один вид повинен бути видно і один прихований:

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

0

Жодне з відповідей рішення не працювало для мене. Я вирішив свої проблеми таким чином (не знаю, чи це правильний шлях?), Тому що у мене виникли проблеми при надто швидкому виклику (коли попередня анімація ще не була закінчена). Я передаю потрібну анімацію за допомогою блоку 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()
                }
        })

    }

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.