presentViewController:animated:YES视图将不会出现,直到用户再次轻按


93

我的出现了一些奇怪的行为presentViewController:animated:completion。我要做的本质上是一个猜谜游戏。

我有一个UIViewController包含UITableView(frequencyTableView)的(frequencyViewController)。当用户点击questionTableView中包含正确答案的行时,应实例化一个视图(correctViewController),并且其视图应从屏幕底部作为模式视图向上滑动。这告诉用户他们的答案正确,并重置其后面的frequencyViewController以准备下一个问题。按下按钮以显示下一个问题时,会关闭correctViewController。

每次都可以正常工作,只要presentViewController:animated:completion有has,即可立即显示正确的ViewController的视图animated:NO

如果设置animated:YES,则会初始化correctViewController并调用viewDidLoad。但是viewWillAppear,不会调用viewDidAppear和的from的完成块presentViewController:animated:completion。该应用程序只是坐在那里,仍然显示frequencyViewController,直到我再次点击。现在,将调用viewWillAppear,viewDidAppear和完成块。

我进行了更多调查,不仅仅是让它继续下去的另一个水龙头。看来,如果我倾斜或摇动我的iPhone,这也可能导致它触发viewWillLoad等。这就像它在等待用户输入的其他任何内容之前,都会继续前进。这是在真实的iPhone上和模拟器中发生的,我通过将摇指令发送到模拟器来证明这一点。

我真的对此无所适从……我非常感谢任何人都可以提供的任何帮助。

谢谢

这是我的代码。很简单...

这是questionViewController中的代码,充当questionTableView的委托

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

    if (indexPath.row != [self.frequencyModel currentFrequencyIndex])
    {
        // If guess was wrong, then mark the selection as incorrect
        NSLog(@"Incorrect Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]);
        UITableViewCell *cell = [self.frequencyTableView cellForRowAtIndexPath:indexPath];
        [cell setBackgroundColor:[UIColor colorWithRed:240/255.0f green:110/255.0f blue:103/255.0f alpha:1.0f]];            
    }
    else
    {
        // If guess was correct, show correct view
        NSLog(@"Correct Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]);
        self.correctViewController = [[HFBCorrectViewController alloc] init];
        self.correctViewController.delegate = self;
        [self presentViewController:self.correctViewController animated:YES completion:^(void){
            NSLog(@"Completed Presenting correctViewController");
            [self setUpViewForNextQuestion];
        }];
    }
}

这是正确的ViewController的整体

@implementation HFBCorrectViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self)
    {
        // Custom initialization
        NSLog(@"[HFBCorrectViewController initWithNibName:bundle:]");
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    NSLog(@"[HFBCorrectViewController viewDidLoad]");
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    NSLog(@"[HFBCorrectViewController viewDidAppear]");
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)close:(id)sender
{
    NSLog(@"[HFBCorrectViewController close:sender:]");
    [self.delegate didDismissCorrectViewController];
}


@end

编辑:

我之前发现了这个问题:UITableView和presentViewController需要2次点击才能显示

而且,如果我将didSelectRow代码更改为此,它在动画方面的工作时间很长……但是它很凌乱,而且对于为什么它一开始不起作用没有任何意义。所以我不认为这是答案...

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

    if (indexPath.row != [self.frequencyModel currentFrequencyIndex])
    {
        // If guess was wrong, then mark the selection as incorrect
        NSLog(@"Incorrect Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]);
        UITableViewCell *cell = [self.frequencyTableView cellForRowAtIndexPath:indexPath];
        [cell setBackgroundColor:[UIColor colorWithRed:240/255.0f green:110/255.0f blue:103/255.0f alpha:1.0f]];
        // [cell setAccessoryType:(UITableViewCellAccessoryType)]

    }
    else
    {
        // If guess was correct, show correct view
        NSLog(@"Correct Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]);

        ////////////////////////////
        // BELOW HERE ARE THE CHANGES
        [self performSelector:@selector(showCorrectViewController:) withObject:nil afterDelay:0];
    }
}

-(void)showCorrectViewController:(id)sender
{
    self.correctViewController = [[HFBCorrectViewController alloc] init];
    self.correctViewController.delegate = self;
    self.correctViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
    [self presentViewController:self.correctViewController animated:YES completion:^(void){
        NSLog(@"Completed Presenting correctViewController");
        [self setUpViewForNextQuestion];
    }];
}

1
我也有同样的问题。我与NSLogs进行了核对,发现我没有调用任何需要很长时间才能执行的方法。看来,presentViewController:应该触发的动画只是一个很大的延迟。这似乎是iOS 7中的错误,Apple Dev论坛中也对此进行了讨论。
Theo 2014年

我也有同样的问题。似乎是一个iOS7错误-在iOS6上不会发生。同样,就我而言,它仅在我打开演示视图控制器后第一次发生。@Theo,您可以提供指向Apple Dev论坛的链接吗?
AX

Answers:


158

我今天也遇到了同样的问题。我研究了这个主题,似乎它与主运行循环处于睡眠状态有关。

实际上,这是一个非常微妙的错误,因为如果您的代码中包含最少的反馈动画,计时器等,则此问题将不会浮出水面,因为这些源将使runloop保持活动状态。我已经通过使用发现问题UITableViewCell有它selectionStyle设置为UITableViewCellSelectionStyleNone,所以没有选择动画触发行选择处理程序追着runloop。

要修复该问题(直到Apple采取措施),您可以通过以下几种方式触发主运行循环:

干扰最小的解决方案是致电CFRunLoopWakeUp

[self presentViewController:vc animated:YES completion:nil];
CFRunLoopWakeUp(CFRunLoopGetCurrent());

或者,您可以将一个空块排队到主队列中:

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

这很有趣,但是如果您摇晃设备,它也会触发主循环(它必须处理运动事件)。与水龙头相同,但包含在原始问题中:)此外,如果系统更新状态栏(例如时钟更新,WiFi信号强度更改等),这也将唤醒主循环并显示视图控制器。

对于有兴趣的人,我编写了该问题的最小演示项目以验证运行循环假设:https : //github.com/tzahola/present-bug

我还向Apple报告了该错误。


谢谢塔玛斯。这是很棒的信息。它解释了为什么有时我需要点击以启动运行循环,或者有时只是等待才能起作用。我将其标记为解决方案,因为在错误修复之前,我们似乎还无法做其他任何事情。
HalfNormalled

苹果挖了一个坑,我掉进去,感觉很疼。
OpenThread

7
天哪,这太好笑了!顺便说一句,这个错误仍然存​​在。
igrrik '16

1
我有完全相同的问题,确实很烦人,幸运的是,此问题已得到解决。
标记

1
确认此问题仍在iOS 13中发生
Eric Horacek

69

请查看以下内容:https : //devforums.apple.com/thread/201431 如果您不想全部阅读-对于某些人(包括我)来说,解决方案是presentViewController在主线程上显式进行调用:

Swift 4.2:

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

目标C:

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

可能是iOS7弄乱了中的线程didSelectRowAtIndexPath


哇-这对我有用。我也看到了延误。我不明白为什么这是必要的。感谢您发布此信息。
drudru 2014年

4
向苹果公司提交了有关此问题的雷达:rdar:// 19563577 openradar.appspot.com/19563577
mluisbrown 2015年

1
我在iOS 8上无法即时解决此问题-它始终报告位于主线程上,但我仍然看到所描述的问题。
罗伯特

7

我通过使用以下代码在Swift 3.0中绕过了它:

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

1
它解决了问题。我遇到了同样的问题,因为我正在调用present闭包回调。
杰里米·皮德诺


0

我很想知道是什么[self setUpViewForNextQuestion];

您可以尝试[self.correctViewController.view setNeedsDisplay];在中的完成代码块结尾处调用presentViewController


现在,setUpViewForNextQuestion只是在表视图上调用reloadData(如果由于不正确的猜测而将所有背景颜色都更改为红色,则将所有背景颜色更改为白色)。但是那里的任何变化会有所不同吗?问题是,正确的Controller直到再次使用时才会出现,因此,直到第二次点击后,才调用此完成块。
HalfNormalled 2014年

我确实尝试过您的建议,但没有任何区别。就像我说的那样,该视图不会出现,因此在第二次点击或摇动手势之后才调用完成块。
HalfNormalled

嗯。对于初学者,我将使用断点来确认直到第二次点击才调用您的演示代码。(相对于在第一次点击时被调用,但直到稍后才在屏幕上绘制)。如果后者是正确的,请尝试强制您的演示在主线程上进行。当您使用“ performSelectorWithDelay:0”时,将强制应用程序等待运行循环的下一次迭代,然后再执行。它可能与线程相关。
尼克

谢谢,尼克。如何使用断点检查?目前,我在想NSLog来检查何时调用每个方法。这就是我现在所拥有的:点击1 Correct Guess: 1 kHz 18:04:57.173 Frequency[641:60b] [HFBCorrectViewController initWithNibName:bundle:] 18:04:57.177 Frequency[641:60b] [HFBCorrectViewController viewDidLoad] 现在什么都没有发生,直到:点击218:05:00.515 Frequency[641:60b] [HFBCorrectViewController viewDidAppear] 18:05:00.516 Frequency[641:60b] Completed Presenting correctViewController 18:05:00.517 Frequency[641:60b] [HFBFrequencyViewController setUpViewForNextQuestion]
HalfNormalled 2014年

抱歉,应该更加明确。我了解如何使用断点。我得到了与NSLog相同的故事。我在正确的ViewController中的viewDidLoad和viewDidAppear上设置了断点。它在viewDidLoad上中断,然后当我继续时,它坐下来等待另一次点击,然后在viewDidAppear上中断。有时似乎不需要轻按,它是基于时间的,到我逐步查看其他要调用的内容时,就可以调用viewDidLoad了。
HalfNormalled

0

我用解决问题的UIViewController方法编写了扩展(类别)。感谢AX和NSHipster提供的实现提示(swift / Objective-c)。

迅速

extension UIViewController {

 override public class func initialize() {
    struct DispatchToken {
      static var token: dispatch_once_t = 0
    }
    if self != UIViewController.self {
      return
    }
    dispatch_once(&DispatchToken.token) {
      let originalSelector = Selector("presentViewController:animated:completion:")
      let swizzledSelector = Selector("wrappedPresentViewController:animated:completion:")

      let originalMethod = class_getInstanceMethod(self, originalSelector)
      let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)

      let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))

      if didAddMethod {
        class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
      }
      else {
        method_exchangeImplementations(originalMethod, swizzledMethod)
      }
    }
  }

  func wrappedPresentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) {
    dispatch_async(dispatch_get_main_queue()) {
      self.wrappedPresentViewController(viewControllerToPresent, animated: flag, completion: completion)
    }
  }
}  

物镜

#import <objc/runtime.h>

@implementation UIViewController (Swizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(presentViewController:animated:completion:);
        SEL swizzledSelector = @selector(wrappedPresentViewController:animated:completion:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)wrappedPresentViewController:(UIViewController *)viewControllerToPresent 
                             animated:(BOOL)flag 
                           completion:(void (^ __nullable)(void))completion {
    dispatch_async(dispatch_get_main_queue(),^{
        [self wrappedPresentViewController:viewControllerToPresent
                                  animated:flag 
                                completion:completion];
    });

}

@end

0

检查故事板上的单元格是否具有“选择” =“无”

如果是这样,请将其更改为蓝色或灰色,并且应该可以使用


0

XCode Vesion:9.4.1,Swift 4.1

就我而言,这发生在我点击单元格并移动到另一个视图时。我调试到更深的地方,似乎viewDidAppear因为包含以下代码而发生在内部

if let indexPath = tableView.indexPathForSelectedRow {
   tableView.deselectRow(at: indexPath, animated: true)
}

然后我在上面的代码段中添加prepare(for segue: UIStoryboardSegue, sender: Any?)并完美工作。

根据我的经验,我的解决方案是,如果我们希望再次从第二个视图返回时对表视图进行任何新的更改(例如,重新加载表,取消选择选定的单元格等),请使用委托代替viewDidAppear上面的tableView.deselectRow代码在移动第二个视图控制器之前进行细分

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.