在用户再次点击之前,YES 视图不会出现

我用 presentViewController:animated:completion做了一些奇怪的动作,我做的实际上是一个猜谜游戏。

我有一个包含 UITableViewUIViewController(channel encyViewController)。当用户点击 questions TableView 中包含正确答案的那一行时,视图应该被实例化,并且它的视图应该从屏幕底部向上滑动,作为一个模态视图。这告诉用户他们得到了一个正确的答案,并重置其后面的班次 ViewController,为下一个问题做好准备。在按下一个按钮时,会显示下一个问题。

每次都能正常工作,只要 presentViewController:animated:completionanimated:NO,则会立即显示正确的 ViewController 视图。

如果我设置了 animated:YES,则会初始化 oretViewController 并调用 viewDidLoad。但是,不调用 viewWillAppearviewDidAppear和来自 presentViewController:animated:completion的完成块。该应用程序只是坐在那里,仍然显示的频率视图控制器,直到我作出第二次点击。现在,viewWillAppear、 viewDidAppear 和完成块被调用。

我又调查了一下,这不仅仅是另一次轻敲,会导致它继续下去。似乎如果我倾斜或摇晃我的 iPhone,这也可以导致它触发 viewWillLoad 等。它就像是在等待用户输入的任何其他位之后才会进行处理。这发生在真正的 iPhone 和模拟器上,我通过向模拟器发送晃动命令证明了这一点。

我真的不知道该怎么办... 我真的很感激任何人能提供的帮助。

谢谢

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

这是 questions ViewController 中的代码,它充当 questions TableView 的委托

- (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];
}];
}
}

这是整个 rectViewController

@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 和 presViewController 需要点击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];
}];
}
24922 次浏览

I'd be curious to see what [self setUpViewForNextQuestion]; does.

You could try calling [self.correctViewController.view setNeedsDisplay]; at the end of your completion block in presentViewController.

Check this out: https://devforums.apple.com/thread/201431 If you don't want to read it all - the solution for some people (including me) was to make the presentViewController call explicitly on the main thread:

Swift 4.2:

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

Objective-C:

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

Probably iOS7 is messing up the threads in didSelectRowAtIndexPath.

Calling [viewController view] on the view controller being presented did the trick for me.

I've encountered the same issue today. I dug into the topic and it seems that it's related to the main runloop being asleep.

Actually it's a very subtle bug, because if you have the slightest feedback animation, timers, etc. in your code this issue won't surface because the runloop will be kept alive by these sources. I've found the issue by using a UITableViewCell which had its selectionStyle set to UITableViewCellSelectionStyleNone, so that no selection animation triggered the runloop after the row selection handler ran.

To fix it (until Apple does something) you can trigger the main runloop by several means:

The least intrusive solution is to call CFRunLoopWakeUp:

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

Or you can enqueue an empty block to the main queue:

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

It's funny, but if you shake the device, it'll also trigger the main loop (it has to process the motion events). Same thing with taps, but that's included in the original question :) Also, if the system updates the status bar (e.g. the clock updates, the WiFi signal strength changes etc.) that'll also wake up the main loop and present the view controller.

For anyone interested I wrote a minimal demonstration project of the issue to verify the runloop hypothesis: https://github.com/tzahola/present-bug

I've also reported the bug to Apple.

I've wrote extension (category) with method swizzling for UIViewController that solves the issue. Thanks to AXE and NSHipster for implementation hints (swift/objective-c).

Swift

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)
}
}
}

Objective-C

#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

Check if your cell in the storyboard has Selection = none

If so, change it to blue or grey and it should work

I bypassed it in Swift 3.0 by using the following code:

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

XCode Vesion : 9.4.1, Swift 4.1

In my case this happen, when I tap cell and move for the another view. I debug into the deeper and it seems that happen inside viewDidAppear because of contains following code

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

then I added above code segment inside prepare(for segue: UIStoryboardSegue, sender: Any?) and work perfect.

Within my experience, my solution is, If we'll hope to do any new changes(eg. table reload, deselect selected cell etc.) for the tableview when again come back from second view, then use delegate instead of viewDidAppear and using above tableView.deselectRow code segment before moving second view controller