当调用`presentViewController:`时,UIAlertController被移动到屏幕顶部的错误位置

pka*_*amb 37 cocoa-touch uialertview ios ios8 uialertcontroller

从一个视图呈现视图UIAlertController将警报移动到屏幕左上角的一个有缺陷的位置.iOS 8.1,设备和模拟器.

当我们尝试从当前"最顶层"视图中呈现视图时,我们在应用程序中注意到了这一点.如果UIAlertController恰好是最顶层的视图,我们会得到这种行为.我们已经更改了我们的代码以简单地忽略UIAlertControllers,但我发布这个以防其他人遇到同样的问题(因为我找不到任何东西).

我们已将此分离为一个简单的测试项目,该问题底部的完整代码.

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

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

警报已移至屏幕顶部

当dismissViewControllerAnimated:动画结束,警报已被移动甚至进一步进入屏幕的左上角边距:

警报已移至屏幕的左上边缘

完整代码:

- (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
}
Run Code Online (Sandbox Code Playgroud)

上面的代码中是否有任何导致此问题的内容?是否存在任何可以从UIAlertController无错误地呈现视图的替代方案?

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

pka*_*amb 12

rdar:// 19037589被Apple关闭

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

没有计划根据以下内容解决此问题:

这不受支持,请避免在UIAlertController上展示.

我们现在正在结束这份报告.

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

对于可能影响此问题的任何更新,请务必定期检查新的Apple版本.

  • "这是不受支持的.请花一生的时间重新构建以前稳定的架构" (13认同)

小智 12

我遇到过一种情况,有时模态视图会出现在警报之上(愚蠢的情况,我知道),UIAlertController可能出现在左上角(就像原始问题的第二个截图),我发现了单线解决方案似乎有效.对于即将在UIAlertController上呈现的控制器,更改其模式表示样式,如下所示:

viewControllerToBePresented.modalPresentationStyle = .OverFullScreen
Run Code Online (Sandbox Code Playgroud)

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

  • 至少,在iOS 11中无法正常工作. (2认同)

Car*_*erg 6

这有点令人失望......将警报移动到UIViewControllers,但随后禁止对它们进行常规使用.我在一个类似的应用程序上工作 - 它有时需要跳转到一个新的用户上下文,并且这样做会在那里提供一个新的视图控制器.实际上,警报是视图控制器在这种情况下几乎更好,因为它们将被保留.但是现在我们已经切换到UIViewControllers,我们看到相同的位移.

我们可能必须提出一个不同的解决方案(可能使用不同的窗口),如果顶级是UIAlertController,我们可能会避免显示.但是,可以恢复正确的定位.这可能不是一个好主意,因为如果Apple改变了他们的屏幕定位,代码可能会破坏,但是如果非常需要这个功能,则下面的子类似乎可以工作(在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
Run Code Online (Sandbox Code Playgroud)

Apple可能会认为视图定位是私有的,因此以这种方式恢复它可能不是最好的主意,但它现在可以使用.也许可以在-presentViewController:animated:的覆盖中存储旧框架,并简单地恢复它而不是重新计算.

可以调整UIAlertController本身来完成与上面相同的操作,这也包括你无法控制的代码所呈现的UIAlertControllers,但我更喜欢只在那些苹果要修复的bug的地方使用swizzles(因此有是一个可以删除混合的时代,我们允许现有的代码"正常工作",而不是为了解决错误而解决问题).但是,如果这是苹果公司不会解决的问题(如他们的回复所示,如此处的另一个答案所述),那么通常有一个单独的类来修改行为更安全,因此您只在已知情况下使用它.

  • 非常好的修复!我已经尝试过这段代码,我已将此代码放在`UIAlertController`类别中,所有系统警报都会自动修复. (2认同)

小智 5

我认为您只应该对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
Run Code Online (Sandbox Code Playgroud)


joe*_*els 5

我也有这个问题。如果在呈现UIAlertController的同时呈现视图控制器,则警报将转到左上方。

我的解决方法是在viewDidLayoutSubviews中刷新UIAlertController视图的中心。通过子类化UIAlertController实现。

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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • _UIAlertController 类旨在按原样使用,不支持子类化。此类的视图层次结构是私有的,不得修改。_ https://developer.apple.com/documentation/uikit/uialertcontroller#//apple_ref/occ/cl/UIAlertController (2认同)