模态视图控制器呈现延迟问题

53

我有一个基于选项卡栏的应用程序。所有5个选项卡中都有导航控制器,其根视图控制器实例是自定义视图控制器并已正确设置。这可以正常加载。其中几个视图控制器包含表格视图。当用户在表格视图中选择一行时,我想向用户显示模态视图控制器。 (相关部分的)didSelectRowAtIndexPath委托方法如下所示:

SampleSelectorViewController *sampleVC = [[SampleSelectorViewController alloc] init];
[self presentViewController:sampleVC animated:YES completion:NULL];

模态视图控制器出现,但是出现的延迟非常明显。有时甚至需要用户第二次点击行才会出现。我已经验证过的几件事情是:

  • 当用户点击行时,表视图的didSelectRowAtIndexPath方法被调用
  • didSelectRowAtIndexPath方法不包含任何阻塞调用。没有进行任何网络操作,模态视图控制器的设置也不涉及任何处理密集型任务。它显示的数据是静态的。
  • 如果我将新的视图控制器推入导航堆栈中(其他所有内容保持完全相同),它就可以完美地工作,没有任何延迟。只有在以模态方式呈现时才会遇到延迟。

有什么想法/建议吗?


出于好奇,使用 animated:NO 会同样慢吗? - pbasdf
有趣。我遇到了同样的问题,即模态呈现被延迟(或必须点击屏幕才能出现)。在我的情况下,它不是直接触发的,而是间接地由didSelectRowAtIndexPath触发。这会调用一个委托方法,该方法又调用一个委托方法,最终进行模态呈现。嗯... - Alex Bollbach
听起来和我遇到的情况非常相似。您介意分享一下您是如何解决这个问题的吗?我以前从未见过这个问题,也没有再次出现过,而且这个场景在项目中已经不存在了,所以我自己也不能提供太多帮助。 - Numan Tariq
1
在iOS 11和Xcode 9上也遇到了同样的问题,目前还没有解决。 - Stas
旧的线程,但尝试使用modalPresentationStyle = .overCurrentContext。这对我有用。 - CZ54
显示剩余2条评论
10个回答

76

tableView:didSelectRowAtIndexPath:方法中调用presentViewController:animated:completion似乎有问题。使用Instruments的Time Profiler时,很难找到任何明显的问题。有时我的模态视图会在不到一秒钟的时间内弹出,而有时需要4秒甚至9秒的时间。

我认为这与底层的UIPresentationController和布局有关。当在Time Profiler中选择从点击行到看到模态呈现之间的时间段时,这是我看到的少数几个东西之一。

存在一个描述此问题的报告:http://openradar.appspot.com/19563577

解决方法很简单但并不完美:稍微延迟呈现以避免导致减速的争议行为。

dispatch_async(dispatch_get_main_queue(), ^{
   [self presentViewController:nav animated:YES completion:nil];
});

1
谢谢你。你的修复方法对我有用,尽管我同意这有点不太令人满意。 - Rogare
1
感谢azsromej的回复。我在其他一些事情上有点卡住了。我会尽快验证修复并接受您的答案,但在我能够这样做之前可能需要一段时间。 - Numan Tariq
这对我有用,https://dev59.com/tF0a5IYBdhLWcg3w48Ln。 - DogCoffee
就像east所说的那样,在主线程上使用异步调度调用来包装演示代码解决了这个问题。非常感谢azsromej的发现! :) - D6mi
我猜这个方法能够起作用的原因是苹果公司在后台线程中调用了tableView:didSelectRowAtIndexPath:方法。而dispatch_async将该调用移回到了主线程(即UI线程)。我认为苹果公司在后台线程中调用该方法是一个bug,但也许他们有某种需要这样做的原因。 - aepryus
显示剩余4条评论

11

如果您在tableView(:didSelectRowAt:)中调用present(:animated:completion:),并且选定的tableview单元格的selectionStyle == .none,并且出现了奇怪的行为,请尝试在tableView(_:didSelectRowAt:)中的任何操作之前调用tableView.deselectRow(at:animated:)。

有帮助吗?


1
你能解释一下这种行为吗?我也遇到了。顺便说一下,解决方案已经修复了它。谢谢。 - Vlad Pulichev

7

Swift 4: 您可以按以下方式使用。

DispatchQueue.main.async {
            let popUpVc = Utilities.viewController(name: "TwoBtnPopUpViewController", onStoryboard: "Login") as? TwoBtnPopUpViewController
            self.present(popUpVc!, animated: true, completion: nil)
        }

对我而言,它有效。


6

我猜你也将单元格的selectionStyle设置为UITableViewCellSelectionStyleNone。我将其更改为UITableViewCellSelectionStyleDefault,这样就可以完美地工作。


3
我也遇到了从tableView:didSelectRowAtIndexPath:方法中呈现时的奇怪延迟,看起来像是苹果的一个bug。不过这个解决方案似乎很有效。
CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Fixes a bug where the main thread may be asleep, especially when using UITableViewCellSelectionStyleNone

3

您应当从根视图控制器 (例如:customTabBarRootViewController) 模态地显示它。保留一个引用,并使用该引用控制器来显示它。


当你说root vc时,你是指我的窗口中的root vc,这在这种情况下将是我的标签栏控制器吗? 此外,如果可能的话,我非常想知道它的原因。 - Numan Tariq
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Gilad
是的,我指的是选项卡控制器,或者包含它的控制器。 - Gilad
顺便提一下,如果在显示的视图控制器上有“重代码”,请尝试使用dispatch将其包装起来:dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 在此处放置您的重代码 =) }); - Gilad
你的延迟有多长? - Gilad
显示剩余3条评论

1
根据@Y.Bonafons的评论,在Swift中,您可以尝试像这样操作,(适用于Swift 4.x和5.0)
DispatchQueue.main.async {

                self.showAction() //Show what you need to present
            }

1

Swift 3中的解决方案

在SampleSelectorViewController(呈现的视图控制器)中使用以下代码

DispatchQueue.global(qos: .background).async {

// Write your code

}

6
您需要在主队列中进行调度。所以请使用:DispatchQueue.main.async { } - Y.Bonafons

1
这种行为的常见问题如下:
在tableView中,如果一个单元格设置了selectionStyle = .none,则在委托方法中使用它似乎与UITableViewController子类化无关(正如http://openradar.appspot.com/19563577中所写)。请注意,不要删除HTML标签。
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)

动画取消选择。
tableView.deselectRow(at: indexPath, animated: true)

这意味着对于非动画单元格也需要进行动画处理。
在这种情况下,后续视图控制器的呈现会有延迟。
有一些解决方法,包括在主线程上使用dispatch_async,但最好不要在代码中对不可选择的单元格调用deselectRow,即使没有动画。

我不再能够访问那个代码库,因此无法测试您的答案,但这似乎是该主题中最有前途的答案! - Numan Tariq

0

尝试使用以下代码,适用于Swift 5.2版本:

DispatchQueue.main.async(execute:{self.present(nav, animated: true)})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接