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;
}
4

2 に答える 2

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を参照してください。)


もう 1 つの方法は、タップ (を調整する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];

iOS 7 では、オブジェクトの現在の動きから新しいアニメーションへのスムーズな移行を確保するためにanimateWithDuration、パラメーターでのレンディションを使用します。initialSpringVelocity:例については、前述のWWDC ビデオを参照してください。


元の答え:

1 秒あたりのタップ数に基づいてビューを調整するには、 を使用でき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) および自動反転 (つまりUIViewAnimationOptionAutoreverse)するアニメーションでこれを行います。

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

シンプルな上下アニメーションの場合は、これで十分です。タイマーは必要ありません。

于 2013-09-23T18:43:40.130 に答える