2013-09-23 2 views
1

Я хочу создать анимацию, которая перемещается вверх или вниз по экрану в соответствии с тем, как быстро пользователь щелкает экран. Проблема, с которой я сталкиваюсь, заключается в том, что я не знаю, как создать бесконечный цикл, поэтому я запускаю таймер, который представляет проблемы. Вот мой текущий код.Создание непрерывной анимации

-(void)setPosOfCider { 
    CGFloat originalY = CGRectGetMinY(cider.frame); 
    float oY = originalY; 
    float posY = averageTapsPerSecond * 100; 
    float dur = 0; 
    dur = (oY - posY)/100; 
    [UIImageView animateWithDuration:dur animations:^(void) { 
     cider.frame = CGRectMake(768, 1024 - posY, 768, 1024); 
    }]; 
} 

Похожие фикс (не работает):

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 


    // Do any additional setup after loading the view, typically from a nib. 
    scroll.pagingEnabled = YES; 
    scroll.scrollEnabled = YES; 
    scroll.contentSize = CGSizeMake(768 * 3, 1024); // 3 pages wide. 
    scroll.delegate = self; 

    self.speedInPointsPerSecond = 200000; 
    self.tapEvents = [NSMutableArray array]; 
} 

-(void)viewDidAppear:(BOOL)animated { 
    [super viewDidAppear:animated]; 
    [self startDisplayLink]; 
} 

-(IBAction)tapped { 
    [self.tapEvents addObject:[NSDate date]]; 

    // if less than two taps, no average speed 

    if ([self.tapEvents count] < 1) 
     return; 

    // only average the last three taps 

    if ([self.tapEvents count] > 2) 
     [self.tapEvents removeObjectAtIndex:0]; 

    // now calculate the average taps per second of the last three taps 

    NSDate *start = self.tapEvents[0]; 
    NSDate *end = [self.tapEvents lastObject]; 

    self.averageTapsPerSecond = [self.tapEvents count]/[end timeIntervalSinceDate:start]; 
} 

- (void)startDisplayLink 
{ 
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)]; 
    self.lastTime = CACurrentMediaTime(); 
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 
} 

- (CGFloat)yAxisValueBasedUponTapsPerSecond 
{ 
    CGFloat y = 1024 - (self.averageTapsPerSecond * 100.0); 

    return y; 
} 

- (void)handleDisplayLink:(CADisplayLink *)displayLink 
{ 
    CFTimeInterval now = CACurrentMediaTime(); 
    CGFloat elapsed = now - self.lastTime; 
    self.lastTime = now; 
    if (elapsed <= 0) return; 

    CGPoint center = self.cider.center; 
    CGFloat destinationY = [self yAxisValueBasedUponTapsPerSecond]; 

    if (center.y == destinationY) 
    { 
     // we don't need to move it at all 

     return; 
    } 
    else if (center.y > destinationY) 
    { 
     // we need to move it up 

     center.y -= self.speedInPointsPerSecond * elapsed; 
     if (center.y < destinationY) 
      center.y = destinationY; 
    } 
    else 
    { 
     // we need to move it down 

     center.y += self.speedInPointsPerSecond * elapsed; 
     if (center.y > destinationY) 
      center.y = destinationY; 
    } 

    self.cider.center = center; 
} 

ответ

2

Еще более простой подход состоит в использовании опции UIViewAnimationOptionBeginFromCurrentState при использовании блоков на основе анимации:

[UIView animateWithDuration:2.0 
         delay:0.0 
        options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction 
       animations:^{ 
        // set the new frame 
       } 
       completion:NULL]; 

Это останавливает текущую блочную анимацию и запускает новую из текущего местоположения. Обратите внимание, что эффективный iOS 8 и более поздний, это теперь поведение по умолчанию, как правило, устраняя необходимость в UIViewAnimationOptionBeginFromCurrentState. Фактически, поведение по умолчанию учитывает не только позицию рассматриваемого вида, но и текущую скорость, обеспечивающую плавный переход. Смотрите WWDC 2014 видео Building Interruptible and Responsive Interactions для получения дополнительной информации.)


Другого подходом будет иметь водопроводные краны (которые регулируют averageTapsPerSecond), чтобы остановить любую существующую анимацию, возьмите вид Расчетный frame из презентационного слоя (который отражает состояние зрения середины анимации), а затем просто начать новую анимацию:

// grab the frame from the presentation layer (which is the frame mid-animation) 

CALayer *presentationLayer = self.viewToAnimate.layer.presentationLayer; 
CGRect frame = presentationLayer.frame; 

// stop the animation 

[self.viewToAnimate.layer removeAllAnimations]; 

// set the frame to be location we got from the presentation layer 

self.viewToAnimate.frame = frame; 

// now, starting from that location, animate to the new frame 

[UIView animateWithDuration:3.0 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{ 
    self.viewToAnimate.frame = ...; // set the frame according to the averageTapsPerSecond 
} completion:nil]; 

в прошивке 7 вы бы использовать исполнение animateWithDuration с параметром initialSpringVelocity:, чтобы обеспечить плавный переход от текущего движения объекта к новому анимация. См. Вышеприведенный пример WWDC video.


Оригинальный ответ:

Чтобы настроить вид на основании количества отводов в секунду, вы могли бы использовать CADisplayLink, который, как NSTimer, но идеально подходит для анимации усилий, как это. Нижняя линия, перевести ваш averageTapsPerSecond в y координат, а затем использовать CADisplayLink анимировать перемещение некоторой точки зрения на эту y координате:

#import "ViewController.h" 
#import <QuartzCore/QuartzCore.h> 

@interface ViewController() 

@property (nonatomic, strong) CADisplayLink *displayLink; 
@property (nonatomic) CFTimeInterval lastTime; 

@property (nonatomic) CGFloat speedInPointsPerSecond; 

@property (nonatomic, strong) NSMutableArray *tapEvents; 
@property (nonatomic) CGFloat averageTapsPerSecond; 

@end 

@implementation ViewController 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    self.speedInPointsPerSecond = 200.0; 
    self.tapEvents = [NSMutableArray array]; 

    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)]; 
    [self.view addGestureRecognizer:tap]; 
} 

- (void)handleTap:(UITapGestureRecognizer *)gesture 
{ 
    [self.tapEvents addObject:[NSDate date]]; 

    // if less than two taps, no average speed 

    if ([self.tapEvents count] < 2) 
     return; 

    // only average the last three taps 

    if ([self.tapEvents count] > 3) 
     [self.tapEvents removeObjectAtIndex:0]; 

    // now calculate the average taps per second of the last three taps 

    NSDate *start = self.tapEvents[0]; 
    NSDate *end = [self.tapEvents lastObject]; 

    self.averageTapsPerSecond = [self.tapEvents count]/[end timeIntervalSinceDate:start]; 
} 

- (CGFloat)yAxisValueBasedUponTapsPerSecond 
{ 
    CGFloat y = 480 - self.averageTapsPerSecond * 100.0; 

    return y; 
} 

- (void)viewDidAppear:(BOOL)animated 
{ 
    [super viewDidAppear:animated]; 

    [self startDisplayLink]; 
} 

- (void)viewDidDisappear:(BOOL)animated 
{ 
    [super viewDidDisappear:animated]; 

    [self stopDisplayLink]; 
} 

- (void)startDisplayLink 
{ 
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)]; 
    self.lastTime = CACurrentMediaTime(); 
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 
} 

- (void)stopDisplayLink 
{ 
    [self.displayLink invalidate]; 
    self.displayLink = nil; 
} 

- (void)handleDisplayLink:(CADisplayLink *)displayLink 
{ 
    CFTimeInterval now = CACurrentMediaTime(); 
    CGFloat elapsed = now - self.lastTime; 
    self.lastTime = now; 
    if (elapsed <= 0) return; 

    CGPoint center = self.viewToAnimate.center; 
    CGFloat destinationY = [self yAxisValueBasedUponTapsPerSecond]; 

    if (center.y == destinationY) 
    { 
     // we don't need to move it at all 

     return; 
    } 
    else if (center.y > destinationY) 
    { 
     // we need to move it up 

     center.y -= self.speedInPointsPerSecond * elapsed; 
     if (center.y < destinationY) 
      center.y = destinationY; 
    } 
    else 
    { 
     // we need to move it down 

     center.y += self.speedInPointsPerSecond * elapsed; 
     if (center.y > destinationY) 
      center.y = destinationY; 
    } 

    self.viewToAnimate.center = center; 
} 

@end 

Надеемся, что это иллюстрирует идею.

Кроме того, чтобы использовать вышеизложенное, убедитесь, что вы добавили фреймворк QuartzCore в свой проект (хотя в Xcode 5 это, похоже, делает это для вас).


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

Вы, как правило, сделать это с помощью анимации, которая повторяется (т.е. UIViewAnimationOptionRepeat) и autoreverses (т.е. UIViewAnimationOptionAutoreverse):

[UIView animateWithDuration:1.0 
         delay:0.0 
        options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat 
       animations:^{ 
        cider.frame = ... // specify the end destination here 
       } 
       completion:nil]; 

Для простой анимации вверх-вниз это все, что вам нужно. Нет необходимости в таймерах.

+0

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

+0

@SHERRIECRANE Мой оригинальный ответ показывает, как сделать простую повторяющуюся анимацию. Начните с одной позиции, сделайте 'animateWithDuration', указав конечную позицию, и повторите и автореверсируйте. Если вы хотите изменить скорость или конечное местоположение (я не был уверен, что вы пытались сделать), вы бы отказались от стандартных анимаций и анимировали себя, используя 'CADisplayLink'. См. Мой пересмотренный ответ. – Rob

+0

Я пытаюсь понять CADisplayLink, но он просто постоянно движется вверх, а затем сбрасывается. –

0

Есть одна строка, которую вы должны добавить к ответу Роба.

[UIView animateWithDuration:dur delay:del options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^{ 
    [UIView setAnimationRepeatCount:HUGE_VAL]; 

    //Your code here 

} completion:nil]; 

Если вам нужно остановить эту анимацию, используйте эту строку (не забудьте добавить рамки QuartzCore):

[view.layer removeAllAnimations]; 
+1

Нет, если вы используете 'UIViewAnimationOptionRepeat', нет необходимости устанавливать количество итераций. Это необходимо, только если вы выполняете анимацию, отличную от блока. Я думаю, что проблема заключается в том, что OP хочет изменить скорость анимации, что, я думаю, подталкивает ее в мир «CADisplayLink» (или таймеров). – Rob

+0

Вы правы. Для этой дополнительной функции таймер может быть самым гибким способом. – ArturOlszak

+0

Согласовано. Честно говоря, 'CADisplayLink' часто рекомендуется в пользу таймеров для анимации, но я думаю, что вы правы. – Rob

Смежные вопросы