Как я могу связать основные анимации для разных слоев один за другим?

У меня есть scrollView с включенным пейджингом и числом N страниц, которые являются UIViews как подвид прокрутки.

Я пытаюсь сделать следующее:

Пользователь прокручивается до номера страницы n. В этот момент 7 CALayers, которые ранее были добавлены к номеру страницы n (т.е. на страницу [[scrollView subviews] objectAtIndex: n-1].layer subLayers]) затухают один за другим.

Но я не могу понять, как сделать CALayers fadeIn последовательно. Далеко я пробовал следующие 3 подхода от моего метода делегирования контроллера: (предположим, что у меня есть массив для слоев и что их непрозрачность была установлена ​​равной 0 при создании)

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
  int pageNumber = floor(self.scrollView.contentOffset.x / self.scrollView.frame.size.width);
  if(pageNumber == (n-1))
  {
    int timeOffset = 0;

    [CATransaction begin];
    for(CALayer *layer in layerArray)
    {
      CABasicAnimation *a = [CABasicAnimation animationWithKeyPath:@"opacity"];
      a.duration = 6;
      a.beginTime = timeOffset++;
      a.fromValue = [NSNumber numberWithFloat:0.];
      a.toValue = [NSNumber numberWithFloat:1.];

      [layer addAnimation:a forKey:nil];
    }
    [CATransaction commit];
  }
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
  int pageNumber = floor(self.scrollView.contentOffset.x / self.scrollView.frame.size.width);
  if(pageNumber == (n-1))
  {
    int timeOffset = 0;

    [CATransaction begin];
    for(CALayer *layer in layerArray)
    {
      CABasicAnimation *a = [CABasicAnimation animation];
      a.duration = 6;
      a.beginTime = timeOffset++;
      [layer addAnimation:a forKey:@"opacity"];
      [layer setOpacity:1];
    }
    [CATransaction commit];
  }
}


- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
  int pageNumber = floor(self.scrollView.contentOffset.x / self.scrollView.frame.size.width);
  if(pageNumber == (n-1))
  {
    int timeOffset = 0;


    for(CALayer *layer in layerArray)
    {
      [CATransaction begin];
      CABasicAnimation *a = [CABasicAnimation animation];
      a.duration = 6;
      a.beginTime = timeOffset++;
      [layer addAnimation:a forKey:@"opacity"];
      [layer setOpacity:1];
    }

    for(CALayer *layer in layerArray)
      [CATransaction commit];
  }
}

Но, похоже, не работает. Когда пользователь прокручивается на правую страницу, все слои становятся видимыми сразу, без значительного затухания и определенно не в каком-либо последовательном порядке.

Любые идеи?

Ответ 1

На самом деле оказывается, что ключ получает текущее время в виде системы отсчета и добавляет любое временное смещение к этому текущему времени. Это работает также для не сгруппированных анимаций.

Например, что-то похожее на этот код приведет к тому, что n слоев (предполагается, что они хранятся в каком-либо массиве) будут постепенно исчезать один за другим, каждый из которых занимает 0,8 с.:

CGFloat timeOffset = 0;

[CATransaction begin];

for (CALayer *layer in layers) {
    CABasicAnimation *a = [CABasicAnimation animationWithKeyPath:@"opacity"];

    a.fromValue = @(0);
    a.toValue = @(1);
    a.fillMode = kCAFillModeForwards;
    a.beginTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil] + timeOffset;
    a.duration = 0.8;
    a.removedOnCompletion = NO;

    [layer addAnimation:a forKey:nil];

    timeOffset += a.duration;
}

[CATransaction commit];

В приведенном выше случае система отсчета - это просто текущее время, когда выполняются вызовы.

Ответ 2

Свойство beginTime для CAAnimation работает, только если CAAnimation является частью CAAnimationGroup. Я думаю, вам также нужно установить свойство duration для CAAnimationGroup достаточно большим, чтобы оно продолжалось до завершения его окончательной анимации.

fooobar.com/questions/527360/...

Ответ 3

В Swift 3 (слои представляют собой массив CALayer или CAShapeLayer)

var timeOffset:Double = 0
for layer in layers {
    let a = CABasicAnimation(keyPath: "path"
    a.fromValue = layer.ovalPathSmall.cgPath
    a.toValue = layer.ovalPathLarge.cgPath
    a.fillMode = kCAFillModeForwards
    a.beginTime = CACurrentMediaTime() + timeOffset
    a.duration = 0.3
    a.isRemovedOnCompletion = false
    layer.add(a, forKey: nil)

    timeOffset += 0.3
}

И в случае, если вам интересно, что такое ovalPathSmall и ovalPathLarge:

ovalPathSmall = UIBezierPath(arcCenter: position, radius: smallRadius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
ovalPathLarge = UIBezierPath(arcCenter: position, radius: largeRadius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)