使用目标c中的信号量创建执行动画并等待完成的方法 [英] Creating a method to perform animations and wait for completion using a semaphore in objective c

查看:148
本文介绍了使用目标c中的信号量创建执行动画并等待完成的方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试创建一个方法,该方法利用UIView的+ animateWithDuration:animations:completion方法来执行动画,并等待完成。我很清楚我可以在完成块中放置通常会出现的代码,但我想避免这种情况,因为在包含更多动画之后会有大量代码,这会让我看到嵌套块。

I am trying to create a method which makes use of UIView's "+animateWithDuration:animations:completion" method to perform animations, and wait for completion. I am well aware that I could just place the code that would normally come after it in a completion block, but I would like to avoid this because there is a substantial amount of code after it including more animations, which would leave me with nested blocks.

我尝试使用信号量实现如下方法,但我不认为这是最好的方法,特别是因为它实际上并没有工作。任何人都可以告诉我我的代码有什么问题,和/或达到同一目标的最佳方式是什么?

I tried to implement this method as below using a semaphore, but I don't think this is the best way to do it, especially because it doesn't actually work. Can anyone tell me what is wrong with my code, and/or what the best way of achieving the same goal is?

+(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);
}

我不确定我的代码有什么问题,但是当我调用方法时如下所示,完成块永远不会运行,我最终陷入困境。

I'm not sure what's wrong with my code but when I call the method as shown below, the completion block never runs and I end up in limbo.

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

--------------------- - - - - - - - - - - - 编辑 - - - - - - - - - - - - - - ----------------------

-------------------------------------------EDIT-------------------------------------------------

如果有人遇到类似问题,您可能会对此感兴趣看到我使用的代码。它使用递归来使用每个完成块,而不必嵌套每个函数调用。

If anyone is facing a similar issue, you might be interested to see the code I used. It uses recursion to make use of each completion block, without having to nest each function call.

+(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]]];


推荐答案

在iOS 7及更高版本中,人们通常会使用关键帧动画实现这种效果。

In iOS 7 and later one would generally employ keyframe animation to achieve this effect.

例如,两个动画序列由四个独立的动画组成,每个动画占整个动画的25%,每个动画序列如下: / p>

For example, a two second animation sequence that is composed of four separate animations that take up 25% of the entire animation each would look like:

[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版本中,你可以排队一个一系列动画有两种方法,但我鼓励你避免在主线程上使用信号量。


In earlier iOS versions, you could queue up a series of animations a couple of ways, but I'd encourage you to avoid using a semaphore on the main thread.

一种方法是将动画包装在并发的 NSOperation 子类中,直到动画才能完成确实。然后,您可以将动画添加到自己的自定义序列队列中:

One approach would be to wrap the animation in a concurrent NSOperation subclass, which doesn't complete until the animation does. You can then add your animations to your own custom serial queue:

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 子类可能如下所示: / p>

The AnimationOperation subclass might look like:

//  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

In我的演示,上面,我使用了一个串行队列。但您也可以使用并发队列,但使用 NSOperation 依赖项来管理各种动画操作之间的关系。这里有很多选择。

In my demonstration, above, I used a serial queue. But you could also use a concurrent queue, but use NSOperation dependencies to manage the relationship between the various animation operations. Lots of options here.

如果要取消动画,可以执行以下操作:

If you want to cancel the animations, you could then do something like the following:

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

您还可以将其合并到自定义<$如果需要,也可以在操作中取消方法。

You could also incorporate this into a custom cancel method in the operation, too, if you want.

这篇关于使用目标c中的信号量创建执行动画并等待完成的方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆