我在使用presentViewController:animated:completion
时遇到了一些奇怪的行为。我所做的实际上是一个猜谜游戏。
我有UIViewController
(frequencyViewController),其中包含UITableView
(frequencyTableView)。当用户点击包含正确答案的questionTableView中的行时,应该实例化视图(correctViewController)并且其视图应该从屏幕底部向上滑动,作为模态视图。这告诉用户他们有正确的答案并重置其背后的frequencyViewController,准备好接下来的问题。按下按钮时,correctViewController被解除,以显示下一个问题。
每次都能正常工作,只要presentViewController:animated:completion
有animated:NO
,就会立即显示correctViewController的视图。
如果我设置animated:YES
,则初始化correctViewController并调用viewDidLoad
。但是,不会调用viewWillAppear
,viewDidAppear
和来自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];
}];
}
}
这是整个correctViewController
@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 and presentViewController takes 2 clicks to display
如果我将我的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];
}];
}
答案 0 :(得分:147)
我今天遇到了同样的问题。我挖掘了这个主题,似乎它与主要的runloop睡着有关。
实际上它是一个非常微妙的错误,因为如果你的代码中有最轻微的反馈动画,定时器等,这个问题就不会浮出水面,因为runloop将会被这些来源保持活着。我通过使用UITableViewCell
设置为selectionStyle
的{{1}}找到了问题,因此在行选择处理程序运行后没有选择动画触发了runloop。
要修复它(直到Apple做某事)你可以通过几种方式触发主runloop:
最少侵入性的解决方案是致电UITableViewCellSelectionStyleNone
:
CFRunLoopWakeUp
或者您可以将空块排入主队列:
[self presentViewController:vc animated:YES completion:nil];
CFRunLoopWakeUp(CFRunLoopGetCurrent());
很有趣,但是如果你摇动设备,它还会触发主循环(它必须处理动作事件)。使用水龙头也是如此,但原始问题中包括了:)同样,如果系统更新状态栏(例如时钟更新,WiFi信号强度改变等),那么也会唤醒主循环并呈现视图控制器。
对于任何有兴趣的人,我写了一个问题的最小示范项目来验证runloop假设:https://github.com/tzahola/present-bug
我还向Apple报告了这个漏洞。
答案 1 :(得分:66)
检查一下: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
中的线程。
答案 2 :(得分:7)
我使用以下代码在Swift 3.0中绕过它:
DispatchQueue.main.async {
self.present(UIViewController(), animated: true, completion: nil)
}
答案 3 :(得分:2)
在呈现的视图控制器上调用[viewController view]
对我来说很有用。
答案 4 :(得分:0)
我很想知道[self setUpViewForNextQuestion];
的作用。
您可以尝试在[self.correctViewController.view setNeedsDisplay];
的完成区块末尾调用presentViewController
。
答案 5 :(得分: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)
}
}
}
<强>目标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
答案 6 :(得分:0)
检查故事板中的单元格是否具有Selection = none
如果是这样,请将其更改为蓝色或灰色,它应该可以正常工作
答案 7 :(得分: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?)
内的上面的代码段中添加了代码,并可以完美地工作。
根据我的经验,我的解决方案是,如果我们希望再次从第二个视图返回时对表视图进行任何新的更改(例如,重新加载表,取消选择选定的单元格等),请使用委托代替{ {1}},并在移动第二个视图控制器之前使用上面的viewDidAppear
代码段