2012-12-15 29 views
12

Próbuję uzyskać animację, która po naciśnięciu przycisku animuje blok w dół, a po zwolnieniu animuje go z powrotem do pierwotnej pozycji, ale nie mogę uzyskać aktualna pozycja animowanego bloku bez względu na wszystko. Tu jest mój kodu:Nie mogę uzyskać aktualnej pozycji CALayera podczas animacji

-(IBAction)moveDown:(id)sender{ 

    CGRect position = [[container.layer presentationLayer] frame]; 

    [movePath moveToPoint:CGPointMake(container.frame.origin.x, position.y)]; 

    [movePath addLineToPoint:CGPointMake(container.frame.origin.x, 310)]; 

    CAKeyframeAnimation *moveAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"]; 
    moveAnim.path = movePath.CGPath; 
    moveAnim.removedOnCompletion = NO; 
    moveAnim.fillMode = kCAFillModeForwards; 


    CAAnimationGroup *animGroup = [CAAnimationGroup animation]; 
    animGroup.animations = [NSArray arrayWithObjects:moveAnim, nil]; 
    animGroup.duration = 2.0; 
    animGroup.removedOnCompletion = NO; 
    animGroup.fillMode = kCAFillModeForwards; 

    [container.layer addAnimation:animGroup forKey:nil]; 
} 
-(IBAction)moveUp:(id)sender{ 
    CGRect position = [[container.layer presentationLayer] frame]; 

    UIBezierPath *movePath = [UIBezierPath bezierPath]; 

    [movePath moveToPoint:CGPointMake(container.frame.origin.x, position.y)]; 

    [movePath addLineToPoint:CGPointMake(container.frame.origin.x, 115)]; 

    CAKeyframeAnimation *moveAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"]; 
    moveAnim.path = movePath.CGPath; 
    moveAnim.removedOnCompletion = NO; 
    moveAnim.fillMode = kCAFillModeForwards; 

    CAAnimationGroup *animGroup = [CAAnimationGroup animation]; 
    animGroup.animations = [NSArray arrayWithObjects:moveAnim, nil]; 
    animGroup.duration = 2.0; 
    animGroup.removedOnCompletion = NO; 
    animGroup.fillMode = kCAFillModeForwards; 

    [container.layer addAnimation:animGroup forKey:nil]; 

} 

Ale linia

CGRect position = [[container.layer presentationLayer] frame]; 

wraca tylko pozycję przeznaczenia nie aktualna pozycja. Muszę zasadniczo podać mi aktualną pozycję kontenera, która animuje się po zwolnieniu przycisku, dzięki czemu mogę wykonać kolejną animację. To, co mam teraz, nie działa.

Odpowiedz

43

Nie zanalizowałem Twojego kodu, aby mieć 100% pewności, dlaczego [[container.layer presentationLayer] frame] może nie zwrócić tego, czego oczekujesz. Ale widzę kilka problemów.

Jednym z oczywistych problemów jest to, że moveDown: nie deklaruje movePath. Jeśli movePath jest zmienną instancji, prawdopodobnie chcesz ją wyczyścić lub utworzyć nową instancję za każdym razem, gdy wywoływana jest nazwa moveDown:, ale nie widzę, żebyś to robił.

Mniej oczywistym problemem jest to, że (sądząc po korzystaniu z removedOnCompletion i fillMode, pomimo korzystania z presentationLayer) najwyraźniej nie rozumiesz, jak działa Core Animation. Okazuje się to zaskakująco powszechne, więc wybacz mi, jeśli się mylę. W każdym razie, czytaj dalej, ponieważ wyjaśnię, jak działa Core Animation, a następnie jak rozwiązać problem.

W programie Core Animation obiekt warstwy, z którym zwykle pracujesz, jest warstwą modelu . Po dołączeniu animacji do warstwy program Core Animation tworzy kopię warstwy modelu o nazwie warstwa prezentacji, a animacja zmienia właściwości warstwy prezentacji w czasie. Animacja nigdy nie zmienia właściwości warstwy modelu.

Po zakończeniu animacji i (domyślnie) usunięciu warstwa prezentacji zostanie zniszczona, a wartości właściwości warstwy modelu zostaną ponownie zastosowane. Tak więc warstwa na ekranie wydaje się "cofać" do swojej pierwotnej pozycji/koloru/cokolwiek.

Powszechnym, ale źle sposób, aby naprawić to, aby ustawić animację na removedOnCompletion do NO i jej fillMode do kCAFillModeForwards. Gdy to zrobisz, warstwa prezentacji będzie się kręcić, więc nie będzie już "cofania" na ekranie. Problem polega na tym, że teraz warstwa prezentacji kręci się z różnymi wartościami niż warstwa modelu. Jeśli poprosisz warstwę modelu (lub widok, który jest jej właścicielem) o wartość animowanej właściwości, otrzymasz wartość inną niż na ekranie. A jeśli spróbujesz animować nieruchomość ponownie, animacja prawdopodobnie rozpocznie się od niewłaściwego miejsca.

Aby animować właściwość warstwy i sprawić, że będzie "trzymać", należy zmienić wartość właściwości warstwy modelu, a następnie zastosować animację. W ten sposób, gdy animacja zostanie usunięta, a warstwa prezentacji zniknie, warstwa na ekranie będzie wyglądać dokładnie tak samo, ponieważ warstwa modelu ma te same wartości właściwości, co w warstwie prezentacji po zakończeniu animacji.

Teraz nie wiem, dlaczego używasz klatki kluczowej do animowania ruchu prostoliniowego lub dlaczego używasz grupy animacji. Nie wydaje się konieczne tutaj.A twoje dwie metody są praktycznie identyczne, więc niech się czynnik wspólny kod:

- (void)animateLayer:(CALayer *)layer toY:(CGFloat)y { 
    CGPoint fromValue = [layer.presentationLayer position]; 
    CGPoint toValue = CGPointMake(fromValue.x, y); 

    layer.position = toValue; 

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; 
    animation.fromValue = [NSValue valueWithCGPoint:fromValue]; 
    animation.toValue = [NSValue valueWithCGPoint:toValue]; 
    animation.duration = 2; 
    [layer addAnimation:animation forKey:animation.keyPath]; 
} 

Zauważ, że dajemy animacji klucza kiedy go dodać do warstwy. Ponieważ za każdym razem używamy tego samego klucza, każda nowa animacja zastąpi (usunie) poprzednią animację, jeśli poprzednia animacja jeszcze się nie zakończyła.

Oczywiście, jak tylko grać z tym, przekonasz się, że jeśli moveUp: gdy moveDown: jest tylko w połowie gotowy, animacja moveUp: pojawi się z połową prędkości, ponieważ nadal ma trwać 2 sekundy ale tylko o połowę mniej w podróży. Powinniśmy naprawdę obliczyć czas w oparciu o odległości do przejechania:

- (void)animateLayer:(CALayer *)layer toY:(CGFloat)y withBaseY:(CGFloat)baseY { 
    CGPoint fromValue = [layer.presentationLayer position]; 
    CGPoint toValue = CGPointMake(fromValue.x, y); 

    layer.position = toValue; 

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; 
    animation.fromValue = [NSValue valueWithCGPoint:fromValue]; 
    animation.toValue = [NSValue valueWithCGPoint:toValue]; 
    animation.duration = 2.0 * (toValue.y - fromValue.y)/(y - baseY); 
    [layer addAnimation:animation forKey:animation.keyPath]; 
} 

Jeśli naprawdę muszą to być animacja keypath w grupie animacji, Twoje pytanie powinno pokazać nam dlaczego trzeba te rzeczy . W każdym razie, to działa z tych rzeczy, TOO:

- (void)animateLayer:(CALayer *)layer toY:(CGFloat)y withBaseY:(CGFloat)baseY { 
    CGPoint fromValue = [layer.presentationLayer position]; 
    CGPoint toValue = CGPointMake(fromValue.x, y); 

    layer.position = toValue; 

    UIBezierPath *path = [UIBezierPath bezierPath]; 
    [path moveToPoint:fromValue]; 
    [path addLineToPoint:toValue]; 

    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; 
    animation.path = path.CGPath; 
    animation.duration = 2.0 * (toValue.y - fromValue.y)/(y - baseY); 

    CAAnimationGroup *group = [CAAnimationGroup animation]; 
    group.duration = animation.duration; 
    group.animations = @[animation]; 
    [layer addAnimation:group forKey:animation.keyPath]; 
} 

można znaleźć pełny kod dla mojego programu testowego in this gist. Po prostu utwórz nowy projekt aplikacji Single View Application i zastąp zawartość zawartego w nim fragmentu na ViewController.m.

+0

Dziękuję bardzo. Jestem nowy w używaniu animacji i to pomogło tonę! Dam mu szansę! – Matt

+4

Zobacz film [Sesja 421 - Core Animation Essentials] (https://developer.apple.com/videos/wwdc/2011/?id=421) wideo z WWDC 2011. To tylko godzina i jest ** wyjątkowo ** informacyjny. –

+0

Niesamowita odpowiedź +1! – Till