Отменить анимацию UIView?

244

Можно ли отменить UIViewанимацию во время ее выполнения? Или мне придется опуститься до уровня CA?

то есть я сделал что-то вроде этого (может быть, тоже установил действие окончания анимации)

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

// set view properties

[UIView commitAnimations];

Но прежде чем анимация завершится, и я получу событие завершения анимации, я хочу отменить его (сократить его). Это возможно? В поисках гугла находят несколько человек, задающих один и тот же вопрос без ответов - и один или два человека гадают, что это невозможно.

philsquared
источник
Вы хотите приостановить анимацию посередине и оставить ее там? Или возвращаться туда, где это было в начале?
Крис Ланди
2
Либо оставив его точно там, где он есть, в середине анимации (на практике я сразу же начну другую анимацию сразу после этого), либо прыгайте прямо к конечной точке. Я предполагаю, что первое более естественно, но в этом случае у меня работает.
philsquared
3
@Chris Hanson: я ценю ваши правки, но мне интересно, каково ваше обоснование для удаления тега iPhone. Это может подразумеваться при прикосновении к какао, но у меня, например, есть фильтр тегов на iPhone, и я бы пропустил это в этом случае.
philsquared
@ Boot To The Head (снова): ваш вопрос и мой собственный ответ на него побудили меня провести еще несколько испытаний в изоляции, и я обнаружил, что если вы запустите новую анимацию до того, как первая закончится, она перейдет к конечной точке перед началом нового.
philsquared
- это отвечает моим непосредственным потребностям (и предполагает, что у меня есть какая-то другая ошибка в моем реальном коде), но я собираюсь оставить этот вопрос открытым, поскольку я знаю, что другие просили то же самое.
philsquared

Ответы:

114

Я делаю это, чтобы создать новую анимацию для вашей конечной точки. Установите очень короткую продолжительность и убедитесь, что вы используете +setAnimationBeginsFromCurrentState:метод, чтобы начать с текущего состояния. Когда вы устанавливаете его в YES, текущая анимация обрывается. Выглядит примерно так:

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

// set view properties

[UIView commitAnimations];
Стивен Дарлингтон
источник
Спасибо Стивен. На самом деле, если вы прочитаете комментарии под моим вопросом, это именно то, что я сделал в конце - но так как вы дали хороший, ясный отчет об этом здесь, я
отмечаю
1
@ Войто Как так? В документах говорится, что использование setAnimationBeginsFromCurrentState:iOS 4 не рекомендуется, но оно все еще есть и должно функционировать.
Стивен Дарлингтон
55
в iOS4 + используйте опцию UIViewAnimationOptionBeginFromCurrentState для animateWithDuration: задержка: опции: анимация: завершение:
Адам Эбербах
Будет ли UIViewAnimationOptionBeginFromCurrentState отменять какую-либо анимацию в процессе или только анимацию, нацеленную на то же свойство? Если это какая-либо анимация, как вы можете заставить ее продолжать новую анимацию в той же точке БЕЗ остановки той же самой анимации в процессе?
Закданс
1
@yourfriendzak, основываясь на кратком тесте, похоже, что он отменяет только анимацию с тем же свойством. Я попытался изменить альфа-представление scrollView, содержимое которого было анимировано, а анимация содержимого продолжена.
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];

Средняя линия будет работать сама по себе, но есть задержка, пока не закончится runloop («момент перерисовки»). Чтобы предотвратить эту задержку, оберните команду в явный блок транзакции, как показано. Это работает при условии, что в текущем цикле выполнения не было никаких других изменений на этом слое.

матовый
источник
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 перейдет к настройке, которую вы только что определили.

tomtaylor
источник
9
Это только частично правда. Он останавливает анимацию, но не мешает запускать методы делегатов, в частности, обработчик animationComplete, поэтому, если у вас есть логика, вы увидите проблемы.
М. Райан
33
Вот для чего нужен «законченный» аргумент -animationDidStop:finished:context:. Если [finished boolValue] == NO, тогда вы знаете, что ваша анимация была как-то отменена.
Ник Фордж
это отличный совет от 7 лет назад! обратите внимание, что есть странное поведение: скажем, вы анимируете альфа назад и вперед, и вы делаете это "перейти к 0". чтобы остановить анимацию, вы не можете установить альфа на ноль; Вы устанавливаете это, чтобы сказать 1.
Толстяк
26

Извините, что воскресил этот ответ, но в iOS 10 все изменилось, и теперь отмена возможна, и вы даже можете отменить изящно!

После iOS 10 вы можете отменить анимацию с UIViewPropertyAnimator !

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

Если вы передадите true, анимация будет отменена и остановится там, где вы ее отменили. Метод завершения не будет вызван. Однако, если вы передадите false, вы несете ответственность за завершение анимации:

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()работает только (Swift)
Lachtan
Ни одно из других решений не сработало. Спасибо, оно сработало для меня.
swapnilagarwal
5

Ничто из вышеперечисленного не решило это для меня, но это помогло: UIViewанимация сразу устанавливает свойство, а затем анимирует его. Останавливает анимацию, когда уровень представления соответствует модели (свойство set).

Я решил свою проблему, которая была «Я хочу анимировать, откуда вы выглядите, как вы выглядите» («вы» означает вид). Если вы хотите ЭТО, то:

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

И это должно быть достойным решением на данный момент.

NazCodezz
источник
5
[UIView setAnimationsEnabled:NO];
// your code here
[UIView setAnimationsEnabled:YES];
ideawu
источник
2

Быстрая версия решения Стивена Дарлингтона

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

// set view properties
UIView.commitAnimations()
lucamegh
источник
1

У меня та же проблема; API не имеют ничего, чтобы отменить какую-то конкретную анимацию.

+ (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
Большое спасибо за эту заметку! У меня действительно есть такая ситуация, когда следующая анимация запускается при вызове animationDidStop. Если вы просто удалите анимацию и сразу добавите новую для того же ключа, я обнаружу, что она не будет работать. Вам нужно подождать, например, пока animationDidStop запустит другую анимацию или что-то еще. Анимация, которая была только что удалена, больше не будет запускать animationDidStop.
РикДжансен
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;
tommybananas
источник
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()
                }
        })

    }

}
sabiland
источник