当它调用“presentViewController:”时,UIAlertController被移动到屏幕顶部的马车位置

UIAlertController呈现视图会将警报移动到屏幕左上angular的马车位置。 iOS 8.1,设备和模拟器。

我们在一个应用程序中注意到这一点,当我们试图从目前的“最顶级”的观点提出一个看法。 如果一个UIAlertController碰巧是最顶层的视图,我们会得到这种行为。 我们已经改变了我们的代码来简单地忽略UIAlertControllers,但我发布这个以防其他人碰到同样的问题(因为我什么都找不到)。

我们已经将这个问题隔离到一个简单的testing项目上,在这个问题的底部给出完整的代码

  1. 在新的Single View Xcode项目中实现viewDidAppear:在视图控制器上。
  2. 提供一个UIAlertController警报。
  3. 警报控制器立即调用presentViewController:animated:completion:显示然后closures另一个视图控制器:

presentViewController:...animation开始的那一刻,UIAlertController被移动到屏幕的左上angular:

警报已移至屏幕顶部

dismissViewControllerAnimated:animation结束时,警报已经进一步移动到屏幕的左上angular:

警报已移动到屏幕的左上角

完整代码:

 - (void)viewDidAppear:(BOOL)animated { // Display a UIAlertController alert NSString *message = @"This UIAlertController will be moved to the top of the screen if it calls `presentViewController:`"; UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"UIAlertController iOS 8.1" message:message preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"I think that's a Bug" style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alert animated:YES completion:nil]; // The UIAlertController should Present and then Dismiss a view UIViewController *viewController = [[UIViewController alloc] init]; viewController.view.backgroundColor = self.view.tintColor; [alert presentViewController:viewController animated:YES completion:^{ dispatch_after(0, dispatch_get_main_queue(), ^{ [viewController dismissViewControllerAnimated:YES completion:nil]; }); }]; // RESULT: // UIAlertController has been moved to the top of the screen. // http://i.imgur.com/KtZobuK.png } 

上面的代码中有没有什么会导致这个问题呢? 是否有任何替代scheme可以从UIAlertController中无错地呈现视图?

rdar:// 19037589
http://openradar.appspot.com/19037589

rdar:// 19037589被苹果closures

苹果开发者关系| 25-Feb-2015 10:52 AM

目前没有计划根据以下内容来解决这个问题:

这不被支持,请避免在UIAlertController上显示。

我们现在正在closures这个报告。

如果您对解决scheme有疑问,或者这对您来说仍然是一个关键问题,请使用该信息更新您的错误报告。

请务必定期检查新的Apple版本是否有任何可能影响此问题的更新。

我遇到了一种情况,有时一个模态视图会出现在一个警报之上(我知道这很愚蠢的情况),UIAlertController可能出现在左上angular(就像原始问题的第二个截图一样),我发现单线解决scheme,似乎工作。 对于即将在UIAlertController中显示的控制器,请更改其模式演示风格,如下所示:

 viewControllerToBePresented.modalPresentationStyle = .OverFullScreen 

这应该在你调用presentViewController(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion completion: (() -> Void)?)之前完成presentViewController(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion completion: (() -> Void)?)

这有点令人失望…将警报移动到UIViewControllers,但是不允许它们的一些常规用法。 我工作的应用程序做了类似的事情 – 有时需要跳转到一个新的用户上下文,这样做提供了一个新的视图控制器在任何地方的顶部。 事实上,警报是视图控制器在这种情况下几乎更好,因为它们将被保留。 但是现在我们已经看到了相同的位移,我们已经切换到UIViewControllers。

我们可能不得不提出一个不同的解决scheme(也许使用不同的窗口),也许我们避免提交,如果顶层是一个UIAlertController。 但是,可以恢复正确的定位。 这可能不是一个好主意,因为如果苹果改变了他们的屏幕位置,代码可能会中断,但是如果这个function是非常需要的话,下面的子类似乎可以工作(在iOS8中)。

 @interface MyAlertController : UIAlertController @end @implementation MyAlertController /* * UIAlertControllers (of alert type, and action sheet type on iPhones/iPods) get placed in crazy * locations when you present a view controller over them. This attempts to restore their original placement. */ - (void)_my_fixupLayout { if (self.preferredStyle == UIAlertControllerStyleAlert && self.view.window) { CGRect myRect = self.view.bounds; CGRect windowRect = [self.view convertRect:myRect toView:nil]; if (!CGRectContainsRect(self.view.window.bounds, windowRect) || CGPointEqualToPoint(windowRect.origin, CGPointZero)) { CGPoint center = self.view.window.center; CGPoint myCenter = [self.view.superview convertPoint:center fromView:nil]; self.view.center = myCenter; } } else if (self.preferredStyle == UIAlertControllerStyleActionSheet && self.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPhone && self.view.window) { CGRect myRect = self.view.bounds; CGRect windowRect = [self.view convertRect:myRect toView:nil]; if (!CGRectContainsRect(self.view.window.bounds, windowRect) || CGPointEqualToPoint(windowRect.origin, CGPointZero)) { UIScreen *screen = self.view.window.screen; CGFloat borderPadding = ((screen.nativeBounds.size.width / screen.nativeScale) - myRect.size.width) / 2.0f; CGRect myFrame = self.view.frame; CGRect superBounds = self.view.superview.bounds; myFrame.origin.x = CGRectGetMidX(superBounds) - myFrame.size.width / 2; myFrame.origin.y = superBounds.size.height - myFrame.size.height - borderPadding; self.view.frame = myFrame; } } } - (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; [self _my_fixupLayout]; } @end 

苹果可能会认为定位是私人的,所以用这种方法恢复它可能不是最好的想法,但是现在就行得通了。 有可能将旧框架存储在-presentViewController:animated:的覆盖中,并简单地恢复,而不是重新计算。

可以调用UIAlertController自身来做与上面相同的事情,也可以覆盖你不能控制的代码所呈现的UIAlertController,但是我更喜欢在苹果要修复的地方使用swizzles(因此在那里是一个可以删除swizzle的时候,我们允许现有的代码“只是工作”,而不是为了一个错误解决方法而弄糟了)。 但是,如果这是苹果公司无法解决的问题(通过他们的答复表明,如另一个答案中所指出的那样),那么让一个单独的类修改行为通常是比较安全的,所以您只能在已知的情况下使用它。

我认为你只需要像这样对UIAlertController进行分类:

 @implementation UIAlertController(UIAlertControllerExtended) - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; if (self.preferredStyle == UIAlertControllerStyleAlert) { __weak UIAlertController *pSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ CGRect screenRect = [[UIScreen mainScreen] bounds]; CGFloat screenWidth = screenRect.size.width; CGFloat screenHeight = screenRect.size.height; [pSelf.view setCenter:CGPointMake(screenWidth / 2.0, screenHeight / 2.0)]; [pSelf.view setNeedsDisplay]; }); } } @end 

我也有这个问题。 如果我在呈现UIAlertController时提供了一个视图控制器,则警报将会显示在左上angular。

我的修复是在viewDidLayoutSubviews中刷新UIAlertController视图的中心; 通过inheritanceUIAlertController来实现。

 class MyBetterAlertController : UIAlertController { override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() let screenBounds = UIScreen.mainScreen().bounds if (preferredStyle == .ActionSheet) { self.view.center = CGPointMake(screenBounds.size.width*0.5, screenBounds.size.height - (self.view.frame.size.height*0.5) - 8) } else { self.view.center = CGPointMake(screenBounds.size.width*0.5, screenBounds.size.height*0.5) } } } 

除了卡尔·林德伯格的回答,还有两种情况也应该考虑到:

  1. 设备旋转
  2. 警报内有文本字段时的键盘高度

所以,对我来说是完全的答案:

 // fix for rotation -(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator { [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) { } completion:^(id<UIViewControllerTransitionCoordinatorContext> context) { [self.view setNeedsLayout]; }]; } // fix for keyboard - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; } -(void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)keyboardWillShow:(NSNotification *)notification { NSDictionary *keyboardUserInfo = [notification userInfo]; CGSize keyboardSize = [[keyboardUserInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size; self.keyboardHeight = keyboardSize.height; [self.view setNeedsLayout]; } - (void)keyboardWillHide:(NSNotification *)notification { self.keyboardHeight = 0; [self.view setNeedsLayout]; } // position layout fix -(void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; [self fixAlertPosition]; } -(void)fixAlertPosition { if (self.preferredStyle == UIAlertControllerStyleAlert && self.view.window) { CGRect myRect = self.view.bounds; CGRect windowRect = [self.view convertRect:myRect toView:nil]; if (!CGRectContainsRect(self.view.window.bounds, windowRect) || CGPointEqualToPoint(windowRect.origin, CGPointZero)) { CGRect myFrame = self.view.frame; CGRect superBounds = self.view.superview.bounds; myFrame.origin.x = CGRectGetMidX(superBounds) - myFrame.size.width / 2; myFrame.origin.y = (superBounds.size.height - myFrame.size.height - self.keyboardHeight) / 2; self.view.frame = myFrame; } } else if (self.preferredStyle == UIAlertControllerStyleActionSheet && self.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPhone && self.view.window) { CGRect myRect = self.view.bounds; CGRect windowRect = [self.view convertRect:myRect toView:nil]; if (!CGRectContainsRect(self.view.window.bounds, windowRect) || CGPointEqualToPoint(windowRect.origin, CGPointZero)) { UIScreen *screen = self.view.window.screen; CGFloat borderPadding = ((screen.nativeBounds.size.width / screen.nativeScale) - myRect.size.width) / 2.0f; CGRect myFrame = self.view.frame; CGRect superBounds = self.view.superview.bounds; myFrame.origin.x = CGRectGetMidX(superBounds) - myFrame.size.width / 2; myFrame.origin.y = superBounds.size.height - myFrame.size.height - borderPadding; self.view.frame = myFrame; } } } 

另外,如果使用类别,则需要以某种方式存储键盘高度,如下所示:

 @interface UIAlertController (Extended) @property (nonatomic) CGFloat keyboardHeight; @end @implementation UIAlertController (Extended) static char keyKeyboardHeight; - (void) setKeyboardHeight:(CGFloat)height { objc_setAssociatedObject (self,&keyKeyboardHeight,@(height),OBJC_ASSOCIATION_RETAIN); } -(CGFloat)keyboardHeight { NSNumber *value = (id)objc_getAssociatedObject(self, &keyKeyboardHeight); return value.floatValue; } @end 

用户manoj.agg将这个答案发布到Open Radar的bug报告中 ,但是说:

不知何故,我没有足够的声誉在Stackoverflow上发布答案。

在这里为他的后代发表他的答案。 我没有testing/评估它。


步骤1:

创build一个从UIViewControllerinheritance的自定义视图控制器,并实现UIPopoverPresentationControllerDelegate

 @interface CustomUIViewController : UIViewController<UITextFieldDelegate, UIPopoverPresentationControllerDelegate> 

第2步:

以全屏显示视图,利用演示文稿popup窗口:

 CustomUIViewController *viewController = [[CustomUIViewController alloc] init]; viewController.view.backgroundColor = self.view.tintColor; viewController.modalPresentationStyle = UIModalPresentationOverFullScreen; UIPopoverPresentationController *popController = viewController.popoverPresentationController; popController.delegate = viewController; [alert presentViewController:viewController animated:YES completion:^{ dispatch_after(0, dispatch_get_main_queue(), ^{ [viewController dismissViewControllerAnimated:YES completion:nil]; }); }]; 

我有一个类似的问题,其中密码input视图需要显示在任何其他视图控制器,包括UIAlertControllers之上。 上面的代码帮助我解决了这个问题。 iOS 8中值得注意的变化是UIAlertControllerinheritance自UIViewControllerUIAlertView并不是这种情况。