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