Как вызвать один блок завершения для вложенной группы вызовов UIView animateWithDuration? - PullRequest
4 голосов
/ 07 декабря 2011

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

-(void) animateStuff:(CGFloat)animationDuration withCompletionBlock:(void) (^)(BOOL)completionBlock {

// encapsulating animation block so that all nested animations start at the same time
 [UIView animateWithDuration:animationDuration animations:^{

    for(MyObject* object in self.array) {
        // this method contains other [UIView animateWithDuration calls...
        [self animationOfUnknownDurationWithObject:object nestedCompletionBlock:^(BOOL finished) {
            // what I effectively what here is:
            if(last animation to finish) {
                completionBlock(YES);
            }
        }];
    }

 }]; // cannot use the completion block for the encapsulating animation block, as it is called straight away
}

Функциональность, предоставляемая использованием dispatch_groups и асинхронного вызова, как описано здесь:
http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html

, очевидно, было бы идеальным, однако вызов UIView animateWithDuration сам по себе является асинхронным, и поэтому его вызов в dispatch_group_async не будет работать должным образом.
Я знаю, что мог бы сделать что-то вроде переменной счетчика __block, которая уменьшается в пределах nestedCompletionBlock какспособ определить, какой из них является последним, но в коде, который у меня есть, это довольно грязно (приведенный выше пример упрощен).
Есть ли хороший способ сделать это?Возможно, какой-то способ сделать animateWithDuration синхронно, чтобы он работал с dispatch_group_async?

Работа на iOS 5.0.

ОБНОВЛЕНИЕ:

@ Rob-
Спасибо за ваш ответ!Это почти решает мою проблему - раньше я не рассматривал CATransaction, думаю, мне следовало немного покопаться, прежде чем спрашивать.:)
Однако остается одна проблема.Как я упоминал ранее, мой пример был упрощен, и остающаяся проблема возникает из-за того факта, что у анимации есть вложенные блоки завершения (т. Е. Для связанных анимаций), которые мне нужно включить во вмещающую транзакцию.
Так, например, если я запускаю следующий код:

-(void) testAnim {
NSArray* testArray = [NSArray arrayWithObjects:self.redView, self.blueView, nil];

[CATransaction begin]; {
    [CATransaction setCompletionBlock:^{
        NSLog(@"All animations complete!");
    }];

    for(UIView* view in testArray) {
        [CATransaction begin]; {

            [CATransaction setCompletionBlock:^{

                [CATransaction begin]; {
                    [CATransaction setCompletionBlock:^{
                        NSLog(@"2nd stage complete");
                    }];

                    NSLog(@"Animation 2nd stage");
                    [UIView animateWithDuration:2 animations:^{
                        setX(view, 100);
                    }];
                } [CATransaction commit];
            }];

            NSLog(@"animation 1st stage");
            [UIView animateWithDuration:2 animations:^{
                setX(view, 150);
            }];
        }[CATransaction commit];
    }

} [CATransaction commit];
}

я получаю следующий вывод:

2011-12-08 15: 11: 35.828 testProj [51550: f803]анимация 1-й этап
2011-12-08 15: 11: 35.831 testProj [51550: f803] анимация 1-й этап
2011-12-08 15: 11: 37.832 testProj [51550: f803] анимация 2-й этап
2011-12-08 15: 11: 37.834 testProj [51550: f803] Анимация 2-й этап
2011-12-08 15: 11: 37.848 testProj [51550: f803] Все анимации завершены!
2011-12-0815: 11: 39.834 testProj [51550: f803] 2-й этап завершен
2011-12-08 15: 11: 39.851 testProj [51550: f803] 2-й этап завершен

Принимая во внимание, что мне нужно"Все анимации завершены!"событие должно быть запущено только после завершения всех внутренних операций.
Возможно, это связано с тем, что я устанавливаю блоки завершения только на уровне класса и, следовательно, не могу связать каждый блок с определенной операцией?
В любом случае, я все еще не совсем понимаю, и использование перегрузки UIView animateWithDuration с собственным аргументом блока завершения просто вызывает ту же проблему.
Есть идеи?

Ответы [ 2 ]

3 голосов
/ 08 декабря 2011

Оберните все это в CATransaction и установите транзакцию completionBlock.Это был мой тест:

static void setX(UIView *view, CGFloat x)
{
    CGRect frame = view.frame;
    frame.origin.x = x;
    view.frame = frame;
}

- (IBAction)startAnimation:(id)sender {
    label.text = @"Animation starting!";
    setX(redView, 0);
    setX(blueView, 0);
    [CATransaction begin]; {
        [CATransaction setCompletionBlock:^{
            label.text = @"Animation complete!";
        }];
        [UIView animateWithDuration:1 animations:^{
            setX(redView, 300);
        }];
        [UIView animateWithDuration:2 animations:^{
            setX(blueView, 300);
        }];
    } [CATransaction commit];
}

Вам нужно будет добавить платформу QuartzCore, если вы этого еще не сделали.

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

Вероятно, было бы проще всего просто запустить каждую последующую анимацию как часть оригинальнойтранзакция с использованием +[UIView animateWithDuration:delay:options:animations:completion:] со значением задержки, которое запускает более позднюю анимацию в нужное время.

1 голос
/ 02 августа 2012

Это может вам помочь.

-(void) testAnim2 {
   // NSArray* testArray = [NSArray arrayWithObjects:self.redView, self.blueView, nil];



    [CATransaction begin]; {
        [CATransaction setCompletionBlock:^{

            [CATransaction begin]; {

                [CATransaction setCompletionBlock:^{
                    NSLog(@"All completion block");

                }];


            }[CATransaction commit];

            NSLog(@"animation 2nd stage");
            [UIView animateWithDuration:2 animations:^{
                setX(blueView, 150);
            }];


        }];
        NSLog(@"animation 1st stage");
        [UIView animateWithDuration:2 animations:^{
            setX(redView, 150);


        }];


    } [CATransaction commit];
}
...