Completion Handler 已执行,但模态视图控制器仍在屏幕上并被引用
Completion Handler is Executed, but Modal View Controller is Still On Screen and Referenced
我的代码正在执行一个冗长的异步操作 (HTTP)。
在开始之前,我展示了一个带有旋转 activity 指示器的自定义模态视图控制器。完成后,我关闭它并显示一个警报控制器以通知用户任务完成。
我的代码如下所示:
// This is a custom modal view controller. It mimics the modal
// presentation style of UIAlertController, but contains a label
// and a spinner:
let progress = ActivityIndicatorAlertController(withTitle:"Syncing...")
self.presentViewController(progress, animated:true, completion:{
WebService.sharedService.syncData(data, completion: {error in
self.dismissViewControllerAnimated(false, completion: {
// <BREAKPOINT>
if error == nil {
// Update UI:
self.reloadTable()
// This method presents a vanilla UIAlertController
// with the specified title and an OK
// button to dismiss:
self.presentModalInformation(title:"Sync Complete", message:"")
上面最后一行执行后,我立即在控制台上收到此警告:
Warning: Attempt to present <UIAlertController: 0x7fae3a57ad60> on
<AppName.MyPresentingViewController: 0x7fae3b02a800> which is already
presenting (null)
尽管正在执行关闭完成处理程序(上面代码中标记为 <BREAKPOINT>
的位置),第一个模态视图控制器(进度指示器)仍然在屏幕上,并且(当然)"completion" 视图永远不会显示控制器。
在用户看来,同步操作 "freezes" 和应用程序变得无响应(挂在模态显示上)。
一些观察:
为了完整起见,这里是呈现第二个模态视图控制器的方法:
func presentModalInformation(title title:String, message:String) {
let alertController = UIAlertController(
title: title,
message: message,
preferredStyle: .Alert)
let okAction = UIAlertAction(title: "OK",
style: UIAlertActionStyle.Default,
handler: nil)
alertController.addAction(okAction)
self.presentViewController(alertController, animated: true, completion: nil)
}
在修改通过 HTTP 执行的任务期间,这个问题开始发生在我无法确定的某个时刻。我没有注意到确切的时间,因为我的连接速度很慢并且每次都测试不同的数据集。
UIKit 抱怨说已经有一个模态显示的视图控制器。 确实,如果我在上面代码中标记为<BREAKPOINT>
的位置停止执行,并通过控制台检查变量,我得到:
(lldb) print self.presentedViewController?.classForCoder
(AnyClass?) $R1 = AppName.ActivityIndicatorAlertController
...尽管所有这些都发生在 关闭完成处理程序中。
我试图用 dispatch_after()
一秒来延迟第二个模态视图控制器的呈现(也许让第一个 "time" 关闭正确吗?)但它不起作用。第一个没有被解雇!
上面代码中的<BREAKPOINT>
是在主线程上执行的("com.apple.main-thread (serial)").
插入对 dismiss...
的 second 调用,作为完成处理程序中的第一件事, 似乎解决了这个问题:
self.presentViewController(progress, animated:true, completion:{
WebService.sharedService.syncData(data, completion: {error in
self.dismissViewControllerAnimated(false, completion: {
// <BREAKPOINT>
self.dismissViewControllerAnimated(false, completion:nil)
// ^^ THIS "FIXES" IT ^^
...这是怎么回事?
附录: ActivityIndicatorAlertController
的源代码在 this repository.
看了这里那里,我找到了原因(虽然我还没有完全理解这个问题)。
我决定在更多位置检查 self.presentedViewController?.classForCoder
的值。这是我得到的:
self.presentViewController(progress, animated:true, completion:{
// (lldb) print self.presentedViewController?.classForCoder
// (AnyClass?) $R0 = UISearchController
WebService.sharedService.syncData(data, completion: {error in
// (lldb) print self.presentedViewController?.classForCoder
// (AnyClass?) $R0 = UISearchController
self.dismissViewControllerAnimated(false, completion: {
// (lldb) print self.presentedViewController?.classForCoder
// (AnyClass?) $R1 = AppName.ActivityIndicatorAlertController
所以,在调用dismiss...
之前,呈现的controller是一个UISearchController
,dismiss后,还剩下ActivityIndicatorAlertController
的实例! (难怪再次调用 dismiss..,
会得到预期的结果!)。
讨论
搜索控制器的身份提示了我,我很快意识到这个问题只发生在 一些 数据集:我从 table 单元格按钮,但为了显示 some 行,我必须先点击 [=] 上方搜索栏的 scope 按钮83=] 视图。当我从一开始就可见的行之一同步时(无 table 视图搜索),问题不会发生。
我想这会以某种方式插入“附加的模态视图控制器层”,所以我需要关闭一次以摆脱搜索控制器,然后再关闭一次 my自定义进度控制器。
然而,我不明白如何。我假设每个视图控制器当时可以呈现一个模态视图控制器(毕竟这是 warning/issue 的全部原因!)
搜索控制器似乎占据了模态表示的“不同级别”:激活时,它被报告为 self.presentedViewController
并消耗一次对 dismiss...
的调用。但这 不会 阻止你以模态方式呈现 另一个 视图控制器(在我的例子中,ActivityIndicatorAlertController
),它会以某种方式“堆积起来” " 在搜索控制器之上,not 报告为 presentedViewController
,并且需要 额外的 调用 dismiss...
,然后你可以再展示一个。
这有意义吗?
所以,我通过在整个事情发生之前添加这段代码来修复:
if let self.presentedViewController != nil {
dismissViewControllerAnimated(false, completion: nil)
// gets rid of occasional UISearchController
}
(...虽然我猜停用搜索控制器应该更正确)
编辑:
确实,调用:
self.resultSearchController.active = false
...在介绍 activity 指示器警报控制器之前也可以工作(并且更正确)。
我的代码正在执行一个冗长的异步操作 (HTTP)。 在开始之前,我展示了一个带有旋转 activity 指示器的自定义模态视图控制器。完成后,我关闭它并显示一个警报控制器以通知用户任务完成。
我的代码如下所示:
// This is a custom modal view controller. It mimics the modal
// presentation style of UIAlertController, but contains a label
// and a spinner:
let progress = ActivityIndicatorAlertController(withTitle:"Syncing...")
self.presentViewController(progress, animated:true, completion:{
WebService.sharedService.syncData(data, completion: {error in
self.dismissViewControllerAnimated(false, completion: {
// <BREAKPOINT>
if error == nil {
// Update UI:
self.reloadTable()
// This method presents a vanilla UIAlertController
// with the specified title and an OK
// button to dismiss:
self.presentModalInformation(title:"Sync Complete", message:"")
上面最后一行执行后,我立即在控制台上收到此警告:
Warning: Attempt to present <UIAlertController: 0x7fae3a57ad60> on <AppName.MyPresentingViewController: 0x7fae3b02a800> which is already presenting (null)
尽管正在执行关闭完成处理程序(上面代码中标记为 <BREAKPOINT>
的位置),第一个模态视图控制器(进度指示器)仍然在屏幕上,并且(当然)"completion" 视图永远不会显示控制器。
在用户看来,同步操作 "freezes" 和应用程序变得无响应(挂在模态显示上)。
一些观察:
为了完整起见,这里是呈现第二个模态视图控制器的方法:
func presentModalInformation(title title:String, message:String) { let alertController = UIAlertController( title: title, message: message, preferredStyle: .Alert) let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil) alertController.addAction(okAction) self.presentViewController(alertController, animated: true, completion: nil) }
在修改通过 HTTP 执行的任务期间,这个问题开始发生在我无法确定的某个时刻。我没有注意到确切的时间,因为我的连接速度很慢并且每次都测试不同的数据集。
UIKit 抱怨说已经有一个模态显示的视图控制器。 确实,如果我在上面代码中标记为
<BREAKPOINT>
的位置停止执行,并通过控制台检查变量,我得到:(lldb) print self.presentedViewController?.classForCoder (AnyClass?) $R1 = AppName.ActivityIndicatorAlertController
...尽管所有这些都发生在 关闭完成处理程序中。
我试图用
dispatch_after()
一秒来延迟第二个模态视图控制器的呈现(也许让第一个 "time" 关闭正确吗?)但它不起作用。第一个没有被解雇!
上面代码中的<BREAKPOINT>
是在主线程上执行的("com.apple.main-thread (serial)").插入对
dismiss...
的 second 调用,作为完成处理程序中的第一件事, 似乎解决了这个问题:self.presentViewController(progress, animated:true, completion:{ WebService.sharedService.syncData(data, completion: {error in self.dismissViewControllerAnimated(false, completion: { // <BREAKPOINT> self.dismissViewControllerAnimated(false, completion:nil) // ^^ THIS "FIXES" IT ^^
...这是怎么回事?
附录: ActivityIndicatorAlertController
的源代码在 this repository.
看了这里那里,我找到了原因(虽然我还没有完全理解这个问题)。
我决定在更多位置检查 self.presentedViewController?.classForCoder
的值。这是我得到的:
self.presentViewController(progress, animated:true, completion:{
// (lldb) print self.presentedViewController?.classForCoder
// (AnyClass?) $R0 = UISearchController
WebService.sharedService.syncData(data, completion: {error in
// (lldb) print self.presentedViewController?.classForCoder
// (AnyClass?) $R0 = UISearchController
self.dismissViewControllerAnimated(false, completion: {
// (lldb) print self.presentedViewController?.classForCoder
// (AnyClass?) $R1 = AppName.ActivityIndicatorAlertController
所以,在调用dismiss...
之前,呈现的controller是一个UISearchController
,dismiss后,还剩下ActivityIndicatorAlertController
的实例! (难怪再次调用 dismiss..,
会得到预期的结果!)。
讨论
搜索控制器的身份提示了我,我很快意识到这个问题只发生在 一些 数据集:我从 table 单元格按钮,但为了显示 some 行,我必须先点击 [=] 上方搜索栏的 scope 按钮83=] 视图。当我从一开始就可见的行之一同步时(无 table 视图搜索),问题不会发生。
我想这会以某种方式插入“附加的模态视图控制器层”,所以我需要关闭一次以摆脱搜索控制器,然后再关闭一次 my自定义进度控制器。
然而,我不明白如何。我假设每个视图控制器当时可以呈现一个模态视图控制器(毕竟这是 warning/issue 的全部原因!)
搜索控制器似乎占据了模态表示的“不同级别”:激活时,它被报告为 self.presentedViewController
并消耗一次对 dismiss...
的调用。但这 不会 阻止你以模态方式呈现 另一个 视图控制器(在我的例子中,ActivityIndicatorAlertController
),它会以某种方式“堆积起来” " 在搜索控制器之上,not 报告为 presentedViewController
,并且需要 额外的 调用 dismiss...
,然后你可以再展示一个。
这有意义吗?
所以,我通过在整个事情发生之前添加这段代码来修复:
if let self.presentedViewController != nil {
dismissViewControllerAnimated(false, completion: nil)
// gets rid of occasional UISearchController
}
(...虽然我猜停用搜索控制器应该更正确)
编辑:
确实,调用:
self.resultSearchController.active = false
...在介绍 activity 指示器警报控制器之前也可以工作(并且更正确)。