iOS 在进入后台时拍摄视图快照的确切时间?
Posted
技术标签:
【中文标题】iOS 在进入后台时拍摄视图快照的确切时间?【英文标题】:The exact moment iOS takes the view snapshot when entering background? 【发布时间】:2011-12-17 18:56:38 【问题描述】:我在按下退出按钮将我的 iPhone 应用程序置于后台,然后通过点击主屏幕上的启动图标重新启动时遇到问题:应用程序的视图确实像我希望的那样返回到其初始状态,但在此之前它会在屏幕上短暂闪烁早期的错误视图状态。
背景
我的主视图基本上由一系列相互关联的 UIAnimateWithDuration 调用组成。每当发生任何中断时,我想要的行为是将动画重置为其初始状态(除非动画全部完成并且应用程序已进入静态最终阶段),并在应用程序返回活动和可见状态时从那里重新开始.
在学习了该主题后,我了解到我需要两种类型的中断处理代码来提供良好的用户体验:“即时”和“流畅”。我有方法 resetAnimation 可以立即将视图属性重置为初始状态,方法 pauseAnimation 可以快速动画到相同状态,并在视图顶部显示“暂停”淡入淡出的附加标签。
双击退出按钮
原因是“双击退出按钮”用例,它实际上并没有隐藏您的视图或将您置于后台状态,它只是向上滚动一点以在底部显示多任务菜单。因此,在这种情况下立即重置视图状态看起来非常难看。动画过渡并告诉用户您已暂停似乎是一个更好的主意。
通过在我的 App Delegate 中实现 applicationWillResignActive 委托方法并从那里调用 pauseAnimation,此案例运行良好且顺利。我通过实现 applicationDidBecomeActive 委托方法并从那里调用我的 resumeAnimation 方法来处理从该多任务菜单返回的问题,该方法会淡出“暂停”标签(如果存在),并从初始状态开始我的动画序列。
这一切都很好,没有任何地方闪烁。
参观另一面
我的应用基于 Xcode“实用程序”模板构建,因此它有一个翻转视图来显示信息/设置。我通过在我的主视图控制器中实现这两个委托方法来处理访问反面并返回主视图:
(void)viewDidDisappear:(BOOL)动画
(void)viewDidAppear:(BOOL)动画
我在 viewDidDisappear 方法中调用了我的 resetAnimation,在 viewDidAppear 中调用了 resumeAnimation。这一切都很好,主视图是从过渡开始到可见状态的初始状态 - 不会意外闪烁任何错误的动画状态。但是:
按下退出按钮并从我的应用图标重新启动(错误部分!)
这就是麻烦的开始。当我按下退出按钮一次并且我的应用程序开始转换到后台时,会发生两件事。首先,applicationWillResignActive 也在这里被调用,所以我的 pauseAnimation 方法也启动了。它不需要,因为这里的过渡不需要是平滑的——视图只是静止的,然后“缩小”以显示主屏幕——但是你能做什么呢?好吧,如果我可以在系统拍摄视图快照的确切时刻之前调用 resetAnimation 也不会造成任何伤害。
无论如何,其次,App Delegate 中的 applicationDidEnterBackground 被调用。我试图从那里调用resetAnimation,以便在应用程序返回时视图处于正确状态,但这似乎不起作用。似乎“快照”已经拍摄,因此,当我点击我的应用程序启动图标并重新启动时,错误的视图状态会在正确的初始状态显示之前在屏幕上短暂闪烁。之后,它工作正常,动画按预期运行,但无论我尝试什么,重新启动时的丑陋闪烁都不会消失。
从根本上说,我所追求的是,系统拍摄此快照的确切时间是什么时候?因此,什么是正确的委托方法或通知处理程序来准备我的观点以拍摄“纪念品照片”?
PS。然后是 default.png,它似乎不仅在首次启动时显示,而且在处理器遇到困难或返回应用程序因其他原因短暂延迟时也会显示。这有点难看,尤其是当您返回到与默认视图完全不同的反面视图时。但这是一个如此核心的 ios 功能,我猜我什至不应该试图弄清楚或控制它:)
编辑:由于人们要求提供实际代码,并且在提出这个问题后我的应用程序已经发布,所以我会在这里发布一些。 (该应用名为 Sweetest Kid,如果您想了解它的实际工作原理,请点击此处:http://itunes.apple.com/app/sweetest-kid/id476637106?mt=8)
这是我的 pauseAnimation 方法 - resetAnimation 几乎相同,只是它的动画调用的持续时间和延迟为零,并且它不显示“暂停”标签。我使用 UIAnimation 来重置值而不是仅仅分配新值的一个原因是,由于某种原因,如果我不使用 UIAnimation,动画就不会停止。无论如何,这是 pauseAnimation 方法:
- (void)pauseAnimation
if (currentAnimationPhase < 6 || currentAnimationPhase == 255)
// 6 means finished, 255 is a short initial animation only showing at first launch
self.paused = YES;
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationOptionAllowUserInteraction |
UIViewAnimationOptionBeginFromCurrentState |
UIViewAnimationOptionCurveEaseInOut |
UIViewAnimationOptionOverrideInheritedCurve |
UIViewAnimationOptionOverrideInheritedDuration
animations:^
pausedView.alpha = 1.0;
cameraImageView.alpha = 0;
mirrorGlowView.alpha = 0;
infoButton.alpha = 1.0;
chantView.alpha = 0;
verseOneLabel.alpha = 1.0;
verseTwoLabel.alpha = 0;
verseThreeLabel.alpha = 0;
shine1View.alpha = stars1View.alpha = stars2View.alpha = 0;
shine1View.transform = CGAffineTransformIdentity;
stars1View.transform = CGAffineTransformIdentity;
stars2View.transform = CGAffineTransformIdentity;
finishedMenuView.alpha = 0;
preparingMagicView.alpha = 0;
completion:^(BOOL finished)
pausedView.alpha = 1.0;
cameraImageView.alpha = 0;
mirrorGlowView.alpha = 0;
infoButton.alpha = 1.0;
chantView.alpha = 0;
verseOneLabel.alpha = 1.0;
verseTwoLabel.alpha = 0;
verseThreeLabel.alpha = 0;
shine1View.alpha = stars1View.alpha = stars2View.alpha = 0;
shine1View.transform = CGAffineTransformIdentity;
stars1View.transform = CGAffineTransformIdentity;
stars2View.transform = CGAffineTransformIdentity;
finishedMenuView.alpha = 0;
preparingMagicView.alpha = 0;
];
askTheMirrorButton.enabled = YES;
againButton.enabled = NO;
shareOnFacebookButton.enabled = NO;
emailButton.enabled = NO;
saveButton.enabled = NO;
currentAnimationPhase = 0;
[[cameraImageView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)]; // To remove the video preview layer
【问题讨论】:
不幸的是,我敢打赌 Apple 不希望您知道/依赖于何时拍摄照片。他们将来可能会改变这一点。对此+1。在某些情况下,他们确实可以选择允许加载某个默认图像(例如在接收推送通知时),但是如果您可以拥有更多控制权会更好。 我也遇到过这种麻烦。 ***.com/questions/4659394/… 很想看到解决方案。 当用户退出到跳板时,为什么不能使用已经为多任务托盘工作的暂停/恢复机制(两次单击“退出”按钮) (一键点击)? @JonGrant:因为 pauseAnimation 是一个持续时间为 0.3 秒的动画,它永远无法完成,因为单击退出按钮一次只会立即停止应用程序的前台进程。此外,需要从 applicationWillResignActive 调用 pauseAnimation 以获得所需的效果,此时无法确定应用程序是否会前进到 applicationDidEnterBackground (换句话说,如果我们真的要进入后台或只是处理暂时中断)。 【参考方案1】:此方法返回后立即截取屏幕截图。我猜你的 -resetAnimation 方法在下一个 runloop 循环中完成,而不是立即完成。 我没有尝试过,但你可以尝试让 runloop 运行,然后稍后返回:
- (void) applicationDidEnterBackground:(UIApplication *)application
// YOUR CODE HERE
// Let the runloop run for a brief moment
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
我希望这会有所帮助, 费边
更新:-pauseAnimation 和 -resetAnimation 的区别
方法:在-applicationWillResignActive:中延迟动画发生,在-applicationDidEnterBackground:中取消延迟动画
- (void) applicationWillResignActive:(UIApplication *)application
// Measure the time between -applicationWillResignActive: and -applicationDidEnterBackground first!
[self performSelector:@selector(pauseAnimation) withObject:nil afterDelay:0.1];
// OTHER CODE HERE
- (void) applicationDidEnterBackground:(UIApplication *)application
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(pauseAnimation) object:nil];
// OTHER CODE HERE
【讨论】:
了解您在 -resetAnimation 中所做的工作也很有帮助。 谢谢,我试试这个。您的猜测似乎是有根据的,这可能就是这种情况,因为我在我的 resetAnimation 方法中也使用了 UIAnimateWithDuration(延迟为 0,持续时间为 0,但仍然是一个动画方法)。我很快会用一些代码 sn-ps 更新我的问题。 Keiser:您的回答实际上可能是在我的原始逻辑中找到错误的关键。在解决双击退出按钮的情况时,我注意到只有在将新值重新分配给另一个动画调用中的动画属性时,我才会停止正在进行的动画,出于某种原因。 我只是在后台用例中调整了相同的样式,但现在我想起来了,根本没有理由在那里使用 UIAnimation,只需立即重置值。这也应该消除辅助线程,并且可能还会自动给我正确的快照图像。我一到开发组就试试。 是的,如果你使用 UIViewAnimation 你应该删除它并且只重置你的视图和视图层次结构。【参考方案2】:感谢@F*** Kreiser,我现在已经运行了一些测试并消除了问题。
总结:Kreiser 说得对:iOS 在方法 applicationDidEnterBackground: 返回之后立即截取屏幕截图 -- 立即意味着,在当前运行循环结束之前。
这意味着,如果您在 didEnterBackground 方法中启动 任何 计划任务,您希望在离开之前完成,您将不得不让当前的 runloop 运行,只要任务可能需要完成。
在我的例子中,计划任务是一个 UIAnimateWithDuration 方法调用——我让自己被它的延迟和持续时间都为 0 的事实搞糊涂了——尽管调用被安排在另一个线程中运行,因此不是能够在 applicationDidEnterBackground 方法结束之前完成。结果:屏幕截图确实是在显示更新到我想要的状态之前截取的 - 并且在重新启动时,此屏幕截图在屏幕上短暂闪烁,导致不必要的闪烁。
此外,为了提供我的问题中解释的“平滑”与“即时”转换行为,Kreiser 建议延迟 applicationWillResignActive: 中的“平滑”转换调用并取消 applicationDidEnterBackground: 中的调用。我注意到两个委托方法之间的延迟在我的例子中大约是 0.005-0.019 秒,所以我应用了很大的余量并使用了 0.05 秒的延迟。
我的赏金,正确答案打勾,我感谢 F***。希望这也能帮助其他处于类似情况的人。
【讨论】:
【参考方案3】:runloop 解决方案实际上会导致应用出现一些问题。
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
如果你转到后台并立即再次打开应用程序,应用程序将变成黑屏。当您第二次重新打开应用程序时,一切都恢复正常。
更好的方法是使用
[CATransaction flush]
这会强制立即应用所有当前事务,并且不会出现导致黑屏的问题。
【讨论】:
【参考方案4】:根据让这个过渡顺利运行对你来说有多重要,你可以用UIApplicationExitsOnSuspend
. 完全取消你的应用程序的多任务处理,然后,你可以保证你的 Default.png 和一个干净的视觉状态.
当然,您必须在退出/启动时保存/恢复状态,而且如果没有有关应用性质的更多信息,很难说这是否值得麻烦。
【讨论】:
感谢您的想法,我不知道这种可能性。但我仍然希望有一些“更清洁”的方式来实现这一目标。【参考方案5】:在 iOS 7 中,[[UIApplication sharedApplication] ignoreSnapshotOnNextApplicationLaunch]
调用完全符合您的需要。
【讨论】:
很高兴知道在我离开 iOS 的时候事情已经取得了进展!以上是关于iOS 在进入后台时拍摄视图快照的确切时间?的主要内容,如果未能解决你的问题,请参考以下文章
在 UIScrollView 中拍摄 CATiledLayer 支持的视图的图像快照