2

UIView の「+animateWithDuration:animations:completion」メソッドを使用してアニメーションを実行し、完了を待つメソッドを作成しようとしています。通常はその後に来るコードを完了ブロックに配置できることをよく知っていますが、その後にアニメーションを含むかなりの量のコードがあり、ネストされたブロックが残るため、これを避けたいと思います.

セマフォを使用して以下のようにこのメソッドを実装しようとしましたが、特に実際には機能しないため、これが最善の方法だとは思いません。私のコードの何が問題なのか、同じ目標を達成するための最良の方法は何か教えてもらえますか?

+(void)animateAndWaitForCompletionWithDuration:(NSTimeInterval)duration animations:(void  (^)(void))animations
{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [UIView animateWithDuration:duration
                 animations:animations
                 completion:^(BOOL finished) {
                     dispatch_semaphore_signal(semaphore);
                 }];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}

コードの何が問題なのかわかりませんが、以下に示すようにメソッドを呼び出すと、完了ブロックが実行されず、行き詰まってしまいます。

[Foo animateAndWaitForCompletionWithDuration:0.5 animations:^{
    //do stuff
}];

- - - - - - - - - - - - - - - - - - - - - -編集 - - - -------------------------------------------

誰かが同様の問題に直面している場合は、私が使用したコードを参照してください。各関数呼び出しをネストすることなく、再帰を使用して各完了ブロックを利用します。

+(void)animateBlocks:(NSArray*)animations withDurations:(NSArray*)durations {
    [Foo animateBlocks:animations withDurations:durations atIndex:0];
}

+(void)animateBlocks:(NSArray*)animations withDurations:(NSArray*)durations atIndex:(NSUInteger)i {
    if (i < [animations count] && i < [durations count]) {
         [UIView animateWithDuration:[(NSNumber*)durations[i] floatValue]
                          animations:(void(^)(void))animations[i]
                          completion:^(BOOL finished){
             [Foo animateBlocks:animations withDurations:durations atIndex:i+1];
         }];
    } 
}

そのように使用できます

[Foo animateBlocks:@[^{/*first animation*/},
                     ^{/*second animation*/}]
     withDurations:@[[NSNumber numberWithFloat:2.0],
                     [NSNumber numberWithFloat:2.0]]];
4

5 に答える 5

10

iOS 7 以降では、通常、キーフレーム アニメーションを使用してこの効果を実現します。

たとえば、それぞれがアニメーション全体の 25% を占める 4 つの個別のアニメーションで構成される 2 秒間のアニメーション シーケンスは、次のようになります。

[UIView animateKeyframesWithDuration:2.0 delay:0.0 options:UIViewKeyframeAnimationOptionRepeat animations:^{
    [UIView addKeyframeWithRelativeStartTime:0.00 relativeDuration:0.25 animations:^{
        viewToAnimate.frame = ...;
    }];
    [UIView addKeyframeWithRelativeStartTime:0.25 relativeDuration:0.25 animations:^{
        viewToAnimate.frame = ...;
    }];
    [UIView addKeyframeWithRelativeStartTime:0.50 relativeDuration:0.25 animations:^{
        viewToAnimate.frame = ...;
    }];
    [UIView addKeyframeWithRelativeStartTime:0.75 relativeDuration:0.25 animations:^{
        viewToAnimate.frame = ...;
    }];
} completion:nil];

以前の iOS バージョンでは、いくつかの方法で一連のアニメーションをキューに入れることができましたが、メイン スレッドでセマフォを使用しないことをお勧めします。

1 つのアプローチは、アニメーションを並行サブクラスでラップすることNSOperationです。これは、アニメーションが完了するまで完了しません。その後、アニメーションを独自のカスタム シリアル キューに追加できます。

NSOperationQueue *animationQueue = [[NSOperationQueue alloc] init];
animationQueue.maxConcurrentOperationCount = 1;

[animationQueue addOperation:[[AnimationOperation alloc] initWithDuration:1.0 delay:0.0 options:0 animations:^{
    viewToAnimate.center = point1;
}]];

[animationQueue addOperation:[[AnimationOperation alloc] initWithDuration:1.0 delay:0.0 options:0 animations:^{
    viewToAnimate.center = point2;
}]];

[animationQueue addOperation:[[AnimationOperation alloc] initWithDuration:1.0 delay:0.0 options:0 animations:^{
    viewToAnimate.center = point3;
}]];

[animationQueue addOperation:[[AnimationOperation alloc] initWithDuration:1.0 delay:0.0 options:0 animations:^{
    viewToAnimate.center = point4;
}]];

サブクラスは次のAnimationOperationようになります。

//  AnimationOperation.h

#import <Foundation/Foundation.h>

@interface AnimationOperation : NSOperation

- (instancetype)initWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations;

@end

//  AnimationOperation.m

#import "AnimationOperation.h"

@interface AnimationOperation ()

@property (nonatomic, readwrite, getter = isFinished)  BOOL finished;
@property (nonatomic, readwrite, getter = isExecuting) BOOL executing;

@property (nonatomic, copy) void (^animations)(void);
@property (nonatomic) UIViewAnimationOptions options;
@property (nonatomic) NSTimeInterval duration;
@property (nonatomic) NSTimeInterval delay;

@end

@implementation AnimationOperation

@synthesize finished  = _finished;
@synthesize executing = _executing;

- (instancetype)initWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations {
    self = [super init];
    if (self) {
        _animations = animations;
        _options    = options;
        _delay      = delay;
        _duration   = duration;
    }
    return self;
}

- (void)start {
    if ([self isCancelled]) {
        self.finished = YES;
        return;
    }

    self.executing = YES;

    [self main];
}

- (void)main {
    dispatch_async(dispatch_get_main_queue(), ^{
        [UIView animateWithDuration:self.duration delay:self.delay options:self.options animations:self.animations completion:^(BOOL finished) {
            [self completeOperation];
        }];
    });
}

#pragma mark - NSOperation methods

- (void)completeOperation {
    if (self.isExecuting) self.executing = NO;
    if (!self.isFinished) self.finished = YES;
}

- (BOOL)isAsynchronous {
    return YES;
}

- (BOOL)isExecuting {
    @synchronized(self) { return _executing; }
}

- (BOOL)isFinished {
    @synchronized(self) { return _finished; }
}

- (void)setExecuting:(BOOL)executing {
    if (_executing != executing) {
        [self willChangeValueForKey:@"isExecuting"];
        @synchronized(self) { _executing = executing; }
        [self didChangeValueForKey:@"isExecuting"];
    }
}

- (void)setFinished:(BOOL)finished {
    if (_finished != finished) {
        [self willChangeValueForKey:@"isFinished"];
        @synchronized(self) { _finished = finished; }
        [self didChangeValueForKey:@"isFinished"];
    }
}

@end

上記のデモンストレーションでは、シリアル キューを使用しました。ただし、同時キューを使用することもできますが、NSOperation依存関係を使用してさまざまなアニメーション操作間の関係を管理します。ここにはたくさんのオプションがあります。


アニメーションをキャンセルしたい場合は、次のようにします。

CALayer *layer = [viewToAnimate.layer presentationLayer];
CGRect frame = layer.frame;                // identify where it is now
[animationQueue cancelAllOperations];      // cancel the operations
[viewToAnimate.layer removeAllAnimations]; // cancel the animations
viewToAnimate.frame = frame;               // set the final position to where it currently is

cancel必要に応じて、これを操作のカスタム メソッドに組み込むこともできます。

于 2014-06-27T17:40:43.567 に答える
0

Jonah が言ったように、メイン スレッドでそれを行う方法はありません。ブロックをネストしたくない場合は、何も悪いことはありませんが、ブロック内にメソッドを配置し、メソッド内に内部ブロックを配置するだけです。内側のブロックにクロージャーが必要な場合は、それらを引数として渡すことができます。

そうすることで、ネストされたブロックへの愛が広がります。;-)

于 2014-06-27T16:47:22.583 に答える