web-dev-qa-db-ja.com

UIViewアニメーションを停止して元に戻す方法は?

UIViewをアニメーション化して、ユーザーがトグルボタンに触れると縮小し、ユーザーがもう一度ボタンに触れると元のサイズに戻るようにしました。これまでのところ、すべてが正常に機能しています。問題は、アニメーションに時間がかかることです。 3秒。その間も、ユーザーがインターフェイスを操作できるようにしたいと思います。したがって、アニメーションの進行中にユーザーがボタンをもう一度タッチすると、アニメーションはその場所で停止し、逆になります。

Apple Q&Aで、すべてのアニメーションをすぐに一時停止する方法を見つけました。

https://developer.Apple.com/library/ios/#qa/qa2009/qa1673.html

しかし、ここからアニメーションを逆にする方法がわかりません(そして最初のアニメーションの残りを省略します)。どうすればこれを達成できますか?

- (IBAction)toggleMeter:(id)sender {
    if (self.myView.hidden) {        
        self.myView.hidden = NO;
        [UIView animateWithDuration:3 animations:^{
            self.myView.transform = expandMatrix;
        } completion:nil];
    } else {
        [UIView animateWithDuration:3 animations:^{
            self.myView.transform = shrinkMatrix;
        } completion:^(BOOL finished) {
            self.myView.hidden = YES;
        }];
    }
}
15
Mischa

以下(プレゼンテーション層から現在の状態を取得し、アニメーションを停止し、保存されたプレゼンテーション層から現在の状態をリセットし、新しいアニメーションを開始する)に加えて、はるかに簡単な解決策があります。

ブロックベースのアニメーションを実行している場合、8.0より前のiOSバージョンでアニメーションを停止して新しいアニメーションを起動する場合は、UIViewAnimationOptionBeginFromCurrentStateオプションを使用するだけです。 (iOS 8では、デフォルトの動作は現在の状態から開始するだけでなく、現在の場所と現在の速度の両方を反映する方法で開始するため、この問題について心配する必要はほとんどありません。 。詳細については、WWDC 2014ビデオ 中断可能で応答性の高い相互作用の構築 を参照してください。)

[UIView animateWithDuration:3.0
                      delay:0.0
                    options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction
                 animations:^{
                     // specify the new `frame`, `transform`, etc. here
                 }
                 completion:NULL];

これを実現するには、現在のアニメーションを停止し、現在のアニメーションを中断したところから新しいアニメーションを開始します。これは Quartz 2D で行うことができます:

  1. QuartzCore.frameworkをプロジェクトに追加します まだ行っていない場合。 (Xcodeの最新バージョンでは、プロジェクトに自動的にリンクされるため、これを明示的に行う必要がないことがよくあります。)

  2. 必要なヘッダーをまだインポートしていない場合はインポートします(これも、Xcodeの最新バージョンでは必要ありません)。

    #import <QuartzCore/QuartzCore.h>
    
  3. コードに既存のアニメーションを停止させます。

    [self.subview.layer removeAllAnimations];
    
  4. 現在のプレゼンテーション層への参照を取得します(つまり、現時点でのビューの状態):

    CALayer *currentLayer = self.subview.layer.presentationLayer;
    
  5. transformの現在の値に従って、frame(またはpresentationLayerなど)をリセットします。

    self.subview.layer.transform = currentLayer.transform;
    
  6. 次に、そのtransform(またはframeなど)から新しい値にアニメーション化します。

    [UIView animateWithDuration:1.0
                          delay:0.0
                        options:UIViewAnimationOptionAllowUserInteraction
                     animations:^{
                         self.subview.layer.transform = newTransform;
                     }
                     completion:NULL];
    

それをすべてまとめると、これが私の変換スケールを2.0xから識別して元に戻すルーチンです。

- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
    CALayer *currentLayer = self.subview.layer.presentationLayer;

    [self.subview.layer removeAllAnimations];

    self.subview.layer.transform = currentLayer.transform;

    CATransform3D newTransform;

    self.large = !self.large;

    if (self.large)
        newTransform = CATransform3DMakeScale(2.0, 2.0, 1.0);
    else
        newTransform = CATransform3DIdentity;

    [UIView animateWithDuration:1.0
                          delay:0.0
                        options:UIViewAnimationOptionAllowUserInteraction
                     animations:^{
                         self.subview.layer.transform = newTransform;
                     }
                     completion:NULL];
}

または、frameサイズを100x100から200x200に切り替えて、元に戻したい場合:

- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
    CALayer *currentLayer = self.subview.layer.presentationLayer;

    [self.subview.layer removeAllAnimations];

    CGRect newFrame = currentLayer.frame;

    self.subview.frame = currentLayer.frame;

    self.large = !self.large;

    if (self.large)
        newFrame.size = CGSizeMake(200.0, 200.0);
    else
        newFrame.size = CGSizeMake(100.0, 100.0);

    [UIView animateWithDuration:1.0
                          delay:0.0
                        options:UIViewAnimationOptionAllowUserInteraction
                     animations:^{
                         self.subview.frame = newFrame;
                     }
                     completion:NULL];
}

ちなみに、通常、非常に速いアニメーションではそれほど重要ではありませんが、あなたのような遅いアニメーションでは、反転アニメーションの長さを、現在のアニメーションで進行した距離と同じに設定することをお勧めします(たとえば、3.0秒のアニメーションに0.5秒入っている場合、元に戻すときは、これまでに行ったアニメーションの小さな部分を元に戻すのに3.0秒かかりたくないでしょうが、0.5秒だけです)。したがって、次のようになります。

- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
    CFTimeInterval duration = kAnimationDuration;             // default the duration to some constant
    CFTimeInterval currentMediaTime = CACurrentMediaTime();   // get the current media time
    static CFTimeInterval lastAnimationStart = 0.0;           // media time of last animation (zero the first time)

    // if we previously animated, then calculate how far along in the previous animation we were
    // and we'll use that for the duration of the reversing animation; if larger than
    // kAnimationDuration that means the prior animation was done, so we'll just use
    // kAnimationDuration for the length of this animation

    if (lastAnimationStart)
        duration = MIN(kAnimationDuration, (currentMediaTime - lastAnimationStart));

    // save our media time for future reference (i.e. future invocations of this routine)

    lastAnimationStart = currentMediaTime;

    // if you want the animations to stay relative the same speed if reversing an ongoing
    // reversal, you can backdate the lastAnimationStart to what the lastAnimationStart
    // would have been if it was a full animation; if you don't do this, if you repeatedly
    // reverse a reversal that is still in progress, they'll incrementally speed up.

    if (duration < kAnimationDuration)
        lastAnimationStart -= (kAnimationDuration - duration);

    // grab the state of the layer as it is right now

    CALayer *currentLayer = self.subview.layer.presentationLayer;

    // cancel any animations in progress

    [self.subview.layer removeAllAnimations];

    // set the transform to be as it is now, possibly in the middle of an animation

    self.subview.layer.transform = currentLayer.transform;

    // toggle our flag as to whether we're looking at large view or not

    self.large = !self.large;

    // set the transform based upon the state of the `large` boolean

    CATransform3D newTransform;

    if (self.large)
        newTransform = CATransform3DMakeScale(2.0, 2.0, 1.0);
    else
        newTransform = CATransform3DIdentity;

    // now animate to our new setting

    [UIView animateWithDuration:duration
                          delay:0.0
                        options:UIViewAnimationOptionAllowUserInteraction
                     animations:^{
                         self.subview.layer.transform = newTransform;
                     }
                     completion:NULL];
}
42
Rob

これを行うために使用できる一般的なトリックがありますが、縮小する別のメソッド(および拡張する別の同様のメソッド)を作成する必要があります。

- (void) shrink {  
    [UIView animateWithDuration:0.3
                     animations:^{
                        self.myView.transform = shrinkALittleBitMatrix;
                     }
                     completion:^(BOOL finished){
                          if (continueShrinking && size>0) {
                              size=size-1;
                              [self shrink];      
                           }
                     }];
}

だから今、トリックは、縮小する3秒のアニメーションをそれぞれ0.3秒の10のアニメーション(またはもちろん10以上)に分割し、アニメーション全体の1/10に縮小することです:shrinkALittleBitMatrix。各アニメーションが終了した後、bool ivar continueShrinkingがtrueで、int ivar sizeが正の場合にのみ同じメソッドを呼び出します(フルサイズのビューはsize = 10になり、ビューはsize = 10になります)。最小サイズの場合はsize = 0になります)。ボタンを押すと、ivar continueShrinkingをFALSEに変更してから、expandを呼び出します。これにより、0.3秒未満でアニメーションが停止します。

さて、あなたは詳細を記入しなければなりませんが、私はそれが役立つことを願っています。

1
Roberto Canogar

最初:ビュー付きのアニメーションを削除またはキャンセルする方法は?

[view.layer removeAllAnimations] 

ビューに多くのアニメーションがある場合。たとえば、1つのアニメーションが上から下に移動し、もう1つのアニメーションが左から右に移動します。

次のような特別なアニメーションをキャンセルまたは削除できます。

[view.layer removeAnimationForKey:@"someKey"];
// the key is you assign when you create a animation 
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"someKey"]; 

これを行うと、アニメーションが停止し、そのデリゲートが呼び出されます。

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag

フラグ== 1の場合、アニメーションが完了したことを示します。フラグ== 0の場合、アニメーションが完了していないことを示します。キャンセルされたり、削除されたりする可能性があります。

Second:なので、このデリゲートメソッドでやりたいことができます。

削除コードの実行時にビューのフレームを取得する場合は、次のようにします。

currentFrame = view.layer.presentationlayer.frame;

注:

現在のフレームを取得してアニメーションを削除すると、ビューも一定期間アニメーション化されるため、currentFrameはデバイス画面の最後のフレームではありません。

現在、この質問を解決することはできません。いつかできれば、この質問を更新します。

0
charvel