UITableView和presentViewController需要点击两次才能显示。

30

我正在使用UITableView来展示各种视图控制器。根据单击的索引,它将呈现不同的视图控制器,然后可以关闭。但是,由于某种原因,我的某个视图控制器需要点击两次才能显示。因此,似乎每隔一次点击才有效。其他视图显示正常,所以表格可能没问题。

在即将显示的ViewController中,我看到的是,第一次单击时,它经过了didSelectRowAtIndexPath,然后在目标视图中触发:ViewWillLoad、ViewLoaded、ViewWillAppear......但不包括ViewDidAppear。第二次单击时,只有ViewDidAppear会触发并显示。第二次单击时,它甚至不会经过didSelectRowAtIndexPath。此外,我可以在屏幕上任何位置单击第二次,它都会显示。

它将继续这样来回往复,似乎只有每隔一次点击才会显示目标视图。你有什么想法为什么会发生这种情况吗?

/* RightPanelViewController */
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
switch(indexPath.row){
    case 0:
        // this view takes two clicks to display
        [self presentViewController:self.delegateRef.savedRidesViewController animated:YES completion:nil];
        break;
    case 1:
        // this works fine, as do others
        [self presentViewController:self.delegateRef.sensorsViewController animated:YES completion:nil];
        break;
    case 2:
       ...
}

/* savedRidesViewController */
- (void) viewWillAppear:(BOOL)animated {
   [super viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated{
   [super viewDidAppear:animated];
}

1
你能添加初始化ViewController的代码吗? - dcorbatta
经过进一步测试,我能够确定它不是呈现的视图控制器本身的问题。这种情况发生在多个不相关的视图上。下面发布了另一个解决方案。 - Miro
我有同样的问题。看起来像是iOS7的一个bug - 在iOS6上不会发生。此外,在我的情况下,它只会在我打开呈现视图控制器后第一次发生。 - AXE
8个回答

57

看这个: https://devforums.apple.com/thread/201431 如果你不想全部阅读 - 对于一些人(包括我),解决方案是在主线程上显式调用presentViewController函数:

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

可能是iOS7在didSelectRowAtIndexPath中搞乱了线程。


你的建议对我很有效,但是你能否请教一下为什么会出现延迟的情况呢?因为我只在主线程上工作,那么为什么还需要调用主线程呢? - user2955351
我不知道。只是在开发论坛上看到的。 我猜想,尽管你没有使用其他线程,但UITableView的方法会调用didSelectRowAtIndexpath:来自这样的线程,所以你在其中做的一切也是在该线程上进行的。这可以通过断点轻松检查。如果你这样做,请告诉我们 :) - AXE
2
这个方案之所以能够奏效,是因为某些东西(很可能是表视图)在didSelect...调用后在主线程上做了一些工作,从而取消了视图控制器的呈现。异步地调用主队列允许运行循环完成(和表视图完成它正在做的事情),然后我们才能呈现视图控制器。这是一个令人不爽的混乱。 - Mikkel Selsøe
1
仍然发生在iOS9.3中。 - marosoaie
有很多关于这个问题的帖子。我认为这不是“非主线程”问题(你可以看到所有操作都发生在主线程上)。我认为Mikkel Selsøe说得很对。这与动作没有触发事件有关。调用deselectRow(就像有人建议的“神奇”修复)只会触发刷新。不确定从已经是主线程的调度是最好的方法。 - bauerMusic
2
这个问题在iOS 11上仍然存在。我同意这不是线程本身的问题,而是一个时间问题。 DispatchQueue.main.async { } 将在下一个runloop上启动任务,从而允许tableView先完成它的事情。 - Au Ris

6
经过更多的调试,我能够确定问题不是出现在视图控制器上,而是与didSelectRowAtIndexPath和presentViewController有关。它开始在我呈现的其他视图上发生,但并不总是出现。我也尝试使用if / else而不是switch,但没有帮助。
最终,我发现这个解决方案可以修复它,但我不完全确定为什么。通过使用没有延迟的performSelector,所有视图都会立即(也许比没有它更快?)可靠地加载每一次。
虽然代码不太干净,但现在确实可靠地工作。我仍然很好奇为什么需要这样做。
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {    
    switch(indexPath.row){
        case 0:
            [self performSelector:@selector(load0:) withObject:nil afterDelay:0];
            break;
        case 1:
            [self performSelector:@selector(load1:) withObject:nil afterDelay:0];
            break;
        case 2:
            [self performSelector:@selector(load2:) withObject:nil afterDelay:0];
            break;
        ...
    }
}

-(void)load0:(id)sender {
     [self presentViewController:self.delegateRef.zonesViewController animated:YES completion:nil];
}
-(void)load1:(id)sender {
     [self presentViewController:self.delegateRef.sensorsViewController animated:YES completion:nil];
}
-(void)load2:(id)sender {
     [self presentViewController:self.delegateRef.savedRidesViewController animated:YES completion:nil];
}

我有同样的问题。我不得不像你一样去做。但我想知道这是为什么。有任何想法吗? - Michal
我还没有完全弄清楚原因。还有其他人吗? - Miro
performSelector:withObject:afterDelay: 的执行时间比直接调用方法稍长。这就是为什么你的失去焦点事件和开始编辑事件突然按正确顺序触发的原因。我建议将 afterDelay 参数设置为 .1 以确保顺序正确。 - Daniel
这个错误在iOS 8上还没有修复。我简直不敢相信。 - Robasan

5

Swift 3 :

DispatchQueue.main.async(execute: {
   self.present(yourViewControllerHere, animated: true, completion: nil)
})

5

3
在Swift和iOS9中,它是这样的:
dispatch_async(dispatch_get_main_queue(), {
    self.performSegueWithIdentifier("OpenBookingDetail", sender: self)
})

DispatchQueue.main.async { self.performSegue(withIdentifier: "OpenBookingDetail", sender: self) } (Swift 3) - Gonzo Oin

2
如前所述,解决方法之一是明确在主线程上运行视图控制器的任何呈现,或者使用performSelector然而,我不清楚为什么这是一个解决方法,因为我认为didSelectRow无论如何都在主线程上运行。直到我阅读了这个答案,我才明白了其中的原因。
有关详细信息,请阅读链接的答案,但简单来说,这会影响选择样式为none的单元格。这将关闭默认情况下出现的(灰色)选定时的动画,这意味着当调用didSelectRow时未触发运行循环。因此,当您去呈现另一个视图控制器时,实际呈现它的动画不会立即在运行循环中发生。
“只需在主线程上运行”并不是直接的解决方法。您实际上只需要触发运行循环,例如通过提供一些代码即可解决。例如: dispatch_async(dispatch_get_main_queue(), {}) [self presentViewController:viewController animated:YES completion:nil] 也可以解决它。这还会触发运行循环,因此现在视图控制器的动画立即发生。这也是为什么调用performSelector有效的原因。

0
可能导致这种行为的一个原因是您的表视图代码,以及启动其他视图控制器的代码没有在主线程上执行。但是,与 UI 相关的所有代码都必须在主线程上执行才能正常工作。
因此,请确保代码在主线程上运行,例如通过设置适当的断点。

0
didSelectRow 方法的结尾处,取消选择它,以便下次可以再次选择。
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
 .
 .
 .
 tableView.deselectRow(at: indexPath, animated: false)
}

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