注意:请不要评论这是不是一个好主意;)我只是在试验,所以想看看结果如何…
我有一个UITextView
和一些inputText
,我将把它们设置为UITextView
的文本。
我想尝试在视觉上滴入inputText
到文本视图,例如,一次一个字母或一次一个单词。
例如(不考虑滴水馈送延迟或执行线程):
for (NSInteger i = 0; i < inputText.length; i++) {
NSString* charAtIndex = [text substringWithRange:NSMakeRange(i, 1)];
_textView.text = [NSString stringWithFormat:@"%@%@", _textView.text, charAtIndex];
}
因此,要构建延迟并查看添加的字符(或单词),每次添加一个,我可以执行以下操作:
dispatch_queue_t backgroundQueue = dispatch_queue_create("background_queue", 0);
for (NSInteger i = 0; i < inputText.length; i++) {
dispatch_async(backgroundQueue, ^{
[NSThread sleepForTimeInterval:0.01f];
NSString* charAtIndex = [inputText substringWithRange:NSMakeRange(i, 1)];
dispatch_async(dispatch_get_main_queue(), ^{
_textView.text = [NSString stringWithFormat:@"%@%@", _textView.text, charAtIndex];
});
});
}
这很好,正如预期的那样,for循环在后台队列上排队了一堆异步操作。
但是我希望上面的操作作为一个更大的、单个的同步可视化的一部分。在添加此代码以滴入UITextView
之前,我只是在UITextView
中设置文本,然后将当前视图(包括UITextView
)动画化到屏幕外。都在主线程上。用户会点击一个按钮,看到整个文本出现,然后视图开始动画离开屏幕,马上。然后,用户转到工作流的下一步。
我正在尝试滴水馈送可视化给人的印象文本视图被填充一个字符(或单词)的时间,但我不希望用户不得不等到每一个最后的字符/单词已添加。所以,我希望看到UITextView
被填充,说,0.3或0.5秒,开始动画,动画视图(包括UITextView
)屏幕外…
就像这样:
dispatch_queue_t backgroundQueue = dispatch_queue_create("background_queue", 0);
for (NSInteger i = 0; i < inputText.length; i++) {
dispatch_async(backgroundQueue, ^{
[NSThread sleepForTimeInterval:0.01f];
NSString* charAtIndex = [inputText substringWithRange:NSMakeRange(i, 1)];
dispatch_async(dispatch_get_main_queue(), ^{
_textView.text = [NSString stringWithFormat:@"%@%@", _textView.text, charAtIndex];
});
});
}
// ...
// Animate the view (including the UITextView) off screen
// ...
// User continues with the main workflow
现在,所有的滴进都在后台异步发生,一旦我添加代码使视图动画化,你就会错过视觉滴进。主线程一直运行到将视图动画移出屏幕。
我不确定如何实现我想要的?
我应该以某种方式中断上面的循环吗?检查从另一个线程更新的标志?
我不能把等待放在主线程上-因为它会阻止滴灌更新到UITextView
…
有什么建议吗?
你可以延迟动画来给滴注时间,像这样:
double delayInSeconds = 0.5f;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// ...
// Animate the view (including the UITextView) off screen
// ...
});
这样用户将看到0.5s的滴灌动画。这是GCD版本,但是你也可以使用这个方法:
[UIView animateWithDuration:0.3f // Set your duration here
delay:0.5f
options:UIViewAnimationOptionCurveEaseOut // Choose the right option here
animations:^{
// Do your animations here.
}
completion:^(BOOL finished){
if (finished) {
// Do your method here after your animation.
}
}];
你最好不要排队的任务负载,然后设置另一个延迟动画。是的,它可以工作,但是它不是那么可维护,并且不能很好地满足未来的其他情况。
相反,可以考虑使用NSTimer
和几个实例变量(可以将它们包装在另一个类中以保持一切干净整洁)。实例变量基本上是当前进程(来自循环的i
)。每次计时器触发时,检查i
-如果文本动画没有完成,使用i
子字符串并更新UI。如果文本动画已经完成,则取消计时器并开始最后的视图动画。
通过这种方式,逻辑是有组织的,易于理解,可重用和可取消的。
根据J. Costa的建议,我仍然使用中央调度的方法。我缺少的部分(我不确定我是否很好地解释了这个需求)是对共享资源的访问。
在下面的代码中,我这样组织它:
-
BOOL
共享资源指示滴注是否继续 - 创建一个串行队列用于读写共享资源
- 滴入发生在后台队列上,并且另外使用串行队列来检查它是否应该继续
- 延迟视图动画是使用
dispatch_after
设置的,当它发生时,它使用串行队列来信号滴注应该停止
NSString* inputText = @"Some meaningful text...";
dispatch_queue_t serialQueue = dispatch_queue_create("serialqueue", DISPATCH_QUEUE_SERIAL);
// the shared resource
_continueDripFeed = YES;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSInteger i = 0; i < inputText.length; i++) {
__block BOOL keepGoing = NO;
dispatch_sync(serialQueue, ^{
// read from the shared resource
keepGoing = _continueDripFeed;
});
if (keepGoing) {
[NSThread sleepForTimeInterval:0.02f];
NSString* charAtIndex = [inputText substringWithRange:NSMakeRange(i, 1)];
dispatch_async(dispatch_get_main_queue(), ^{
_textView.text = [NSString stringWithFormat:@"%@%@", _textView.text, charAtIndex];
});
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
_textView.text = inputText;
});
break;
}
}
});
double delayInSeconds = 0.5f;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
dispatch_sync(serialQueue, ^{
// update the shared resource
_continueDripFeed = NO;
});
[self animateTextViewToFrame:_offScreenFrame];
// Continue with workflow...
});