我正在实现一个通过用户输入动画的健康栏。
这些动画使其上下移动一定量(比如说50个单位)并且是按下按钮的结果。有两个按钮。增加和减少。
我想在健康栏上执行锁定,以便一次只能有一个线程更改它。问题是我陷入了僵局。
我猜是因为一个单独的线程运行另一个线程持有的锁。但是当动画完成时,该锁将会让位。如何实现在[UIView AnimateWithDuration]完成时结束的锁定?
我想知道NSConditionLock
是否可行,但我想尽可能使用NSLocks以避免不必要的复杂性。你推荐什么?
(最终我希望动画“排队”,同时让用户输入继续,但是现在我只想让锁定工作,即使它先阻止输入。)
(嗯,想到它只有一个[UIView AnimateWithDuration]一次运行同一个UIView。第二个调用将中断第一个,导致完成处理程序立即运行第一个。也许第二个锁在第一个有机会解锁之前运行。在这种情况下处理锁定的最佳方法是什么?也许我应该重新审视Grand Central Dispatch但是我想看看是否有更简单的方法。)
在ViewController.h中我声明:
NSLock *_lock;
在ViewController.m中我有:
在loadView中:
_lock = [[NSLock alloc] init];
ViewController.m的其余部分(相关部分):
-(void)tryTheLockWithStr:(NSString *)str
{
LLog(@"\n");
LLog(@" tryTheLock %@..", str);
if ([_lock tryLock] == NO)
{
NSLog(@"LOCKED.");
}
else
{
NSLog(@"free.");
[_lock unlock];
}
}
// TOUCH DECREASE BUTTON
-(void)touchThreadButton1
{
LLog(@" touchThreadButton1..");
[self tryTheLockWithStr:@"beforeLock"];
[_lock lock];
[self tryTheLockWithStr:@"afterLock"];
int changeAmtInt = ((-1) * FILLBAR_CHANGE_AMT);
[self updateFillBar1Value:changeAmtInt];
[UIView animateWithDuration:1.0
delay:0.0
options:(UIViewAnimationOptionTransitionNone|UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionAllowUserInteraction)
animations:
^{
LLog(@" BEGIN animationBlock - val: %d", self.fillBar1Value)
self.fillBar1.frame = CGRectMake(FILLBAR_1_X_ORIGIN,FILLBAR_1_Y_ORIGIN, self.fillBar1Value,30);
}
completion:^(BOOL finished)
{
LLog(@" END animationBlock - val: %d - finished: %@", self.fillBar1Value, (finished ? @"YES" : @"NO"));
[self tryTheLockWithStr:@"beforeUnlock"];
[_lock unlock];
[self tryTheLockWithStr:@"afterUnlock"];
}
];
}
-(void)updateFillBar1Value:(int)changeAmt
{
self.prevFillBar1Value = self.fillBar1Value;
self.fillBar1Value += changeAmt;
if (self.fillBar1Value < FILLBAR_MIN_VALUE)
{
self.fillBar1Value = FILLBAR_MIN_VALUE;
}
else if (self.fillBar1Value > FILLBAR_MAX_VALUE)
{
self.fillBar1Value = FILLBAR_MAX_VALUE;
}
}
输出:
转载指示:点按“降低”一次
touchThreadButton1 ..
tryTheLock beforeLock .. 免费。
tryTheLock afterLock .. 锁定。 BEGIN animationBlock - val:250 END animationBlock - val:250 - 完成:是
tryTheLock beforeUnlock .. 锁定。
tryTheLock afterUnlock .. 免费。
结论:这可以按预期工作。
-
输出:
要重现说明:快速点击“减少”两次(中断初始动画)..
touchThreadButton1 ..
tryTheLock beforeLock .. 免费。
tryTheLock afterLock .. 锁定。 BEGIN animationBlock - val:250 touchThreadButton1 ..
tryTheLock beforeLock .. 锁定。 * - [NSLock lock]:死锁('(null)') * 打破_NSLockError()进行调试。
结论。死锁错误。用户输入被冻结。
答案 0 :(得分:10)
在底部,在我的原始答案中,我描述了一种实现所请求功能的方法(如果您在前一个动画仍在进行时启动动画,则将此后续动画排队到仅在当前动画完成后才开始)。
虽然我会将其保留用于历史目的,但我可能想要提出一种完全不同的方法。具体来说,如果你点击一个应该导致动画的按钮,但是前一个动画仍在进行中,我建议你删除旧动画并立即启动新动画,但这样做是为了新动画从当前离开的地方开始。
在iOS 8之前的iOS版本中,挑战在于,如果您在另一个动画正在进行时启动新动画,则操作系统会立即跳转到当前动画结束的位置并从那里开始新动画
8版之前的iOS版本中的典型解决方案是:
抓取动画视图的presentationLayer
(这是CALayer
的{{1}}的当前状态...如果您查看UIView
当动画正在进行中时,您将看到最终值,我们需要获取当前状态);
从UIView
获取动画属性值的当前值;
删除动画;
将动画属性重置为&#34;当前&#34;值(因此在开始下一个动画之前它似乎没有跳到前一个动画的结尾);
将动画发送到&#34; new&#34;值;
因此,例如,如果您要动画可能正在进行动画的presentationLayer
的更改动画,则可能会执行以下操作:
frame
这完全消除了排队&#34; next&#34;的所有尴尬。动画在&#34;当前&#34;之后运行动画(和其他排队的动画)完成。您最终会得到响应更快的用户界面(例如,您不必在用户所需的动画开始之前等待之前的动画完成)。
在iOS 8中,这个过程非常容易,如果您启动新动画,它通常会从动画属性的当前值开始动画,但也会识别速度此当前动画属性正在改变,导致旧动画和新动画之间的无缝转换。
有关此新iOS 8功能的详细信息,建议您参考WWDC 2014视频Building Interruptible and Responsive Interactions。
为了完整起见,我将在下面保留我原来的答案,因为它试图精确地解决问题中概述的功能(只是使用不同的机制来确保主队列没有被阻止) 。但我真的建议考虑停止当前的动画并以这样一种方式开始新动画,使其从任何正在进行的动画可能停止的地方开始。
原始回答:
我不建议在CALayer *presentationLayer = animatedView.layer.presentationLayer;
CGRect currentFrame = presentationLayer.frame;
[animatedView.layer removeAllAnimations];
animatedView.frame = currentFrame;
[UIView animateWithDuration:1.0 animations:^{
animatedView.frame = newFrame;
}];
(或信号量或任何其他类似机制)中包装动画,因为这会导致阻塞主线程。你永远不想阻止主线程。我认为你对使用串行队列调整操作大小的直觉很有希望。你可能想要一个&#34;调整大小&#34;操作:
在主队列上启动NSLock
动画(所有UI更新必须在主队列上发生);以及
在动画的完成模块中,完成操作(我们不会在此之前完成操作,以确保其他排队的操作不会启动,直到完成此操作)。
我可能会建议调整大小的操作:
SizeOperation.h:
UIView
SizingOperation.m:
@interface SizeOperation : NSOperation
@property (nonatomic) CGFloat sizeChange;
@property (nonatomic, weak) UIView *view;
- (id)initWithSizeChange:(NSInteger)change view:(UIView *)view;
@end
然后为这些操作定义一个队列:
#import "SizeOperation.h"
@interface SizeOperation ()
@property (nonatomic, readwrite, getter = isFinished) BOOL finished;
@property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
@end
@implementation SizeOperation
@synthesize finished = _finished;
@synthesize executing = _executing;
- (id)initWithSizeChange:(NSInteger)change view:(UIView *)view
{
self = [super init];
if (self) {
_sizeChange = change;
_view = view;
}
return self;
}
- (void)start
{
if ([self isCancelled] || self.view == nil) {
self.finished = YES;
return;
}
self.executing = YES;
// note, UI updates *must* take place on the main queue, but in the completion
// block, we'll terminate this particular operation
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[UIView animateWithDuration:2.0 delay:0.0 options:kNilOptions animations:^{
CGRect frame = self.view.frame;
frame.size.width += self.sizeChange;
self.view.frame = frame;
} completion:^(BOOL finished) {
self.finished = YES;
self.executing = NO;
}];
}];
}
#pragma mark - NSOperation methods
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
@end
确保实例化此队列(作为串行队列):
@property (nonatomic, strong) NSOperationQueue *sizeQueue;
然后,任何使相关观点成长的事情都会发生:
self.sizeQueue = [[NSOperationQueue alloc] init];
self.sizeQueue.maxConcurrentOperationCount = 1;
任何使视图缩小的内容都会缩小:
[self.sizeQueue addOperation:[[SizeOperation alloc] initWithSizeChange:+50.0 view:self.barView]];
希望这说明了这个想法。有各种可能的改进:
我让动画效果很慢,所以我可以轻松排队,但你可能会使用更短的值;
如果使用自动布局,您可以调整宽度约束[self.sizeQueue addOperation:[[SizeOperation alloc] initWithSizeChange:-50.0 view:self.barView]];
,并在动画块中调整constant
}而不是layoutIfNeeded
直接调整框架;以及
如果宽度达到某个最大值/最小值,您可能希望添加检查以不执行帧更改。
但关键是使用锁来控制UI更改的动画是不可取的。除了几毫秒之外,你不想要任何可以阻止主队列的东西。动画块太长,无法考虑阻止主队列。因此,使用串行操作队列(如果您有多个需要启动更改的线程,他们只需将操作添加到同一个共享操作队列,从而自动协调从各种不同线程发起的更改)。 / p>