Лучший способ выполнить несколько последовательных анимаций UIView?

У меня есть серия из 8 анимаций UIView, которые появляются непосредственно после загрузки моего представления. Прямо сейчас я выполняю это, используя метод делегата animationDidStop:finished:context, и все работает так, как ожидалось. Проблема в том, что у меня есть новый метод для каждой анимации. Большая часть кода в каждом из этих методов повторяется, только с длительностью анимации и фактическим позиционированием элементов.

Я попытался создать единый метод, который будет вызываться, и иметь контекст, содержащий параметры, необходимые для правильного изменения интерфейса, но он, похоже, рекурсивно называет себя намного больше, чем я его называю:

-(void)animationDidStop:(NSString *)animationID finished:(BOOL)finished context:(void *)context{
    NSNumber *number = (NSNumber *)context;
    int animationStep = [number intValue];
    int nextAnimationStep = animationStep + 1;
    NSNumber *nextAnimationStepNumber = [NSNumber numberWithInt:nextAnimationStep];
    NSLog(@"Animation Step: %i", animationStep);

    CGRect firstFrame = CGRectMake(self.feedsScroll.frame.size.width * 2, 0.0f, self.secondFeedView.view.frame.size.width, self.secondFeedView.view.frame.size.height);
    CGRect thirdFrame = CGRectMake(self.feedsScroll.frame.size.width * 2, 0.0f, self.thirdFeedView.view.frame.size.width, self.thirdFeedView.view.frame.size.height);

    [UIView beginAnimations:nil context:nextAnimationStepNumber];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [UIView setAnimationCurve:UIViewAnimationCurveLinear];
    [UIView setAnimationDelegate:self];

    if (animationStep < 8) 
        [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];

    NSLog(@"Beginning animations");

    switch (animationStep) {
        case 0:
            [UIView setAnimationDuration:.3];
            self.firstFeedView.view.center = CGPointMake(self.firstFeedView.view.center.x + 30, self.firstFeedView.view.center.y);
            break;
        case 1:
            [UIView setAnimationDuration:.3];
            self.firstFeedView.view.center = CGPointMake(self.firstFeedView.view.center.x  - 30, self.firstFeedView.view.center.y);
            break;
        case 2:
            [UIView setAnimationDuration:.3];
            [self.secondFeedView.view setFrame:firstFrame];
            break;
        case 3:
            [UIView setAnimationDuration:.3];
            self.secondFeedView.view.center = CGPointMake(self.secondFeedView.view.center.x + 30, self.firstFeedView.view.center.y);
            break;
        case 4:
            [UIView setAnimationDuration:.3];
            self.secondFeedView.view.center = CGPointMake(self.secondFeedView.view.center.x - 30, self.firstFeedView.view.center.y);
            break;
        case 5:
            NSLog(@"Animation step 6");
            [UIView setAnimationDuration:.5];
            self.firstFeedView.view.center = CGPointMake(self.firstFeedView.view.center.x - 230, self.firstFeedView.view.center.y);
            self.secondFeedView.view.center = CGPointMake(self.secondFeedView.view.center.x - 230, self.firstFeedView.view.center.y);
            break;
        case 6:
            [UIView setAnimationDuration:.5];
            [self.thirdFeedView.view setFrame:thirdFrame];
            break;
        case 7:
            [UIView setAnimationDuration:.3];
            self.thirdFeedView.view.center = CGPointMake(self.thirdFeedView.view.center.x + 30, self.firstFeedView.view.center.y);
            break;
        case 8:
            [UIView setAnimationDuration:.3];
            self.thirdFeedView.view.center = CGPointMake(self.thirdFeedView.view.center.x - 30, self.thirdFeedView.view.center.y);
            break;
        default:
            break;
    }

    [UIView commitAnimations];  
}

Я знаю, что это, вероятно, наивная реализация. Я новичок в разработке iPhone, и я ищу некоторые рекомендации, которые можно применить здесь. Я иду об этом не так?

Ответы

Ответ 1

Если вы захотите подойти к iOS 4.0, новый подход к анимации на основе блоков может сделать эту анимацию тривиальной. Например:

[UIView animateWithDuration:1.0 animations:^{ view.position = CGPointMake(0.0f, 0.0f); } completion:^(BOOL finished){
    [UIView animateWithDuration:0.2 animations:^{ view.alpha = 0.0; } completion:^(BOOL finished){
        [view removeFromSuperview]; }];
}]

приведет к тому, что view будет анимировать (0, 0) в течение 1 секунды, исчезнет на 0,2 секунды, а затем будет удалено из его супервизора. Эти анимации будут последовательными.

Первый блок в методе класса +animateWithDuration:animations:completion: содержит изменения свойств для анимации сразу, а второй - действие, которое необходимо выполнить при завершении анимации. Поскольку этот обратный вызов может содержать другую анимацию такого типа, вы можете вложить их в произвольные цепочки анимаций.

Ответ 2

Вы можете использовать блоки для этой цели и получить очень чистый результат.

NSMutableArray* animationBlocks = [NSMutableArray new];

typedef void(^animationBlock)(BOOL);

// getNextAnimation
// removes the first block in the queue and returns it
animationBlock (^getNextAnimation)() = ^{
    animationBlock block = (animationBlock)[animationBlocks firstObject];
    if (block){
        [animationBlocks removeObjectAtIndex:0];
        return block;
    }else{
         return ^(BOOL finished){};
    }
};

//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
    [UIView animateWithDuration:1.0 animations:^{
        //...animation code...
    } completion: getNextAnimation()];
}];

//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
    [UIView animateWithDuration:1.0 animations:^{
        //...animation code...
    } completion: getNextAnimation()];
}];

//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
    NSLog(@"Multi-step Animation Complete!");
}];

// execute the first block in the queue
getNextAnimation()(YES);

Взято из: http://xibxor.com/objective-c/uiview-animation-without-nested-hell/

Ответ 3

Мы создали компонент для целенаправленности шагов анимации с использованием блоков (CPAnimationSequence на Github). Мы описали мотивацию и обоснование в нашем блоге развития iOS.

Он дает вам очень читаемый код, что-то вроде этого:

[[CPAnimationSequence sequenceWithSteps:
    [CPAnimationStep           for:0.25 animate:^{ self.imageView.alpha = 0.0; }],
    [CPAnimationStep           for:0.25 animate:^{ self.headline.alpha = 0.0;  }],
    [CPAnimationStep           for:0.25 animate:^{ self.content.alpha = 0.0;   }],
    [CPAnimationStep after:1.0 for:0.25 animate:^{ self.headline.alpha = 1.0;  }],
    [CPAnimationStep           for:0.25 animate:^{ self.content.alpha = 1.0;   }],
    nil]
runAnimated:YES];

Ответ 4

"Контекст" - это пустота * и поэтому не сохраняется. Есть несколько вариантов:

  • Сохраните его, когда вы его установили. Autorelease это в начале обратного вызова. Это кажется нехорошим (Objective-C функции должны соответственно сохраняться/сниматься, но, к сожалению, контекст не является id).
  • Сохраните номер в строке: int animationStep = [animationID intValue]; и [UIView beginAnimations:[NSString stringWithFormat:@"%d", nextAnimationStep] context:NULL];. Это также неприятно.
  • Предполагая, что UIKit/CoreAnimation не разыскивает указатель, int animationStep = (int)context; и [UIView beginAnimations:nil context:(void*)nextAnimationStep];. Это нехорошо (и, вероятно, вызывает поведение undefined в соответствии со стандартом C).

В другом примечании finished:(BOOL)finished должно быть finished:(NSNumber*)finished. Они ошиблись в оригинальных документах; вероятно, было проще изменить документы, чтобы отразить API вместо изменения API и иметь магическую поддержку обратной совместимости (хотя передача bool более разумна).

Ответ 5

 // create the view that will execute our animation
 UIImageView* logo = [[UIImageView alloc] initWithFrame:self.view.frame];
 // load all the frames of our animation
 logo.animationImages = [NSArray arrayWithObjects:  
                                 [UIImage imageNamed:@"frame_00001.png"],

                         [UIImage imageNamed:@"frame_00002.png"],
                         [UIImage imageNamed:@"frame_00003.png"],
                         [UIImage imageNamed:@"frame_00004.png"],
                                 [UIImage imageNamed:@"frame_00005"], nil];

 // all frames will execute in 3 seconds
 logo.animationDuration =3.3;
 // repeat the annimation forever
logo.animationRepeatCount = 1;
 // start animating
 [logo startAnimating];
 // add the animation view to the main window 
 [self.view addSubview:logo];
 [logo release]; 

поместите это в ViewDidLoad