Подтвердить что ты не робот

Свойства CAGradientLayer не анимируются в блоке анимации UIView

У меня такое чувство, что я пропускаю что-то элементарное, но какой лучший способ найти его, чем ошибаться в Интернете?

У меня довольно простой интерфейс. Представление для моего UIViewController - это подкласс, чей +layerClass - CAGradientLayer. В зависимости от действий пользователя мне нужно перемещать некоторые элементы пользовательского интерфейса и изменять значения градиента фона. Код выглядит примерно так:

[UIView animateWithDuration:0.3 animations:^{
  self.subview1.frame = CGRectMake(...);
  self.subview2.frame = CGRectMake(...);
  self.subview2.alpha = 0;

  NSArray* newColors = [NSArray arrayWithObjects:
                         (id)firstColor.CGColor,
                         (id)secondColor.CGColor,
                         nil];
  [(CAGradientLayer *)self.layer setColors:newColors];
}];

Проблема заключается в том, что изменения, которые я делаю в этом блоке для подзапросов, оживляют просто отлично (вещи перемещаются и исчезают), но изменение цветов градиента не происходит. Он просто меняет местами.

Теперь в документации говорится, что код Core Animation в блоке анимации не наследует свойства блока (продолжительность, ослабление и т.д.).). Но не так ли, что это вообще не определяет транзакцию анимации? (Импликация документов, по-видимому, заключается в том, что вы получите анимацию по умолчанию, в которой я ее не получаю.)

Нужно ли использовать явный CAAnimation, чтобы сделать это? (И если да, то почему?)

4b9b3361

Ответ 1

Здесь, кажется, есть две вещи. Первый (как правильно указывает Тревис, и указано в документации) заключается в том, что анимации UIKit, похоже, не влияют на неявную анимацию, применяемую к изменениям свойств CALayer. Я думаю, что это странно (UIKit должен использовать Core Animation), но это то, что есть.

Здесь (возможно, очень тупой?) обходной путь для этой проблемы:

  NSTimeInterval duration = 2.0; // slow things down for ease of debugging
  [UIView animateWithDuration:duration animations:^{
    [CATransaction begin];
    [CATransaction setAnimationDuration:duration];

    // ... do stuff to things here ...

    [CATransaction commit];
  }];

Другой ключ заключается в том, что этот слой градиента - это мой уровень представления. Это означает, что мое представление является делегатом слоя (где, если слой градиента был только подслоем, у него не было бы делегата). И реализация UIView -actionForLayer:forKey: возвращает NSNull для события "colors". (Возможно, каждое событие, которое не находится в определенном списке анимаций UIView.)

Добавление следующего кода в мое представление приведет к анимации цвета, как ожидалось:

- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
  id<CAAction> action = [super actionForLayer:layer forKey:event];
  if( [@"colors" isEqualToString:event]
      && (nil == action || (id)[NSNull null] == action) ) {
    action = [CABasicAnimation animationWithKeyPath:event];
  }
  return action;
}

Ответ 2

Вы должны использовать явные CAAnimations, потому что вы меняете значение CALayer. UIViewAnimations работает с свойствами UIView, но не напрямую по их свойствам CALayer...

Собственно, вы должны использовать CABasicAnimation, чтобы вы могли получить доступ к его свойствам fromValue и toValue.

Следующий код должен работать для вас:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [UIView animateWithDuration:2.0f
                          delay:0.0f
                        options:UIViewAnimationCurveEaseInOut
                     animations:^{
                         CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"colors"];
                         animation.duration = 2.0f;
                         animation.delegate = self;
                         animation.fromValue = ((CAGradientLayer *)self.layer).colors;
                         animation.toValue = [NSArray arrayWithObjects:(id)[UIColor blackColor].CGColor,(id)[UIColor whiteColor].CGColor,nil];
                         [self.layer addAnimation:animation forKey:@"animateColors"];
                     }
                     completion:nil];
}

-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    NSString *keyPath = ((CAPropertyAnimation *)anim).keyPath;
    if ([keyPath isEqualToString:@"colors"]) {
        ((CAGradientLayer *)self.layer).colors = ((CABasicAnimation *)anim).toValue;
    }
}

Есть трюк с CAAnimations в том, что вы должны явно установить значение свойства ПОСЛЕ завершения анимации.

Вы делаете это, устанавливая делегат, в этом случае я устанавливаю его на объект, который вызывает анимацию, а затем переопределяет его метод animationDidStop:finished:, чтобы добавить значение цветов CAGradientLayer к их окончательному значению.

Вам также нужно немного выполнить кастинг в методе animationDidStop:, чтобы получить доступ к свойствам анимации.