首页 文章

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

提问于
浏览
31

UIAlertController 呈现视图会将警报移动到屏幕左上角的错误位置 . iOS 8.1,设备和模拟器 .

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

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

  • 在新的Single View Xcode项目中的View Controller上实现 viewDidAppear: .

  • 提出 UIAlertController 警报 .

  • 警报控制器立即调用 presentViewController:animated:completion: 显示然后关闭另一个视图控制器:

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

Alert has moved to top of screen

dismissViewControllerAnimated: 动画结束时,警报已进一步移动到屏幕的左上边缘:

Alert has moved into the top-left margin of the screen

完整代码:

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

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

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

8 回答

  • 1

    rdar://19037589被Apple关闭

    Apple开发者关系| 25-Feb-2015 10:52 AM没有计划根据以下内容解决此问题:不支持此功能,请避免在UIAlertController上展示 . 我们现在正在结束这份报告 . 如果您对解决方案有疑问,或者这对您来说仍然是一个关键问题,请使用该信息更新您的错误报告 . 对于可能影响此问题的任何更新,请务必定期检查新的Apple版本 .

  • 9

    我遇到过一种情况,有时模态视图会出现在警报之上(愚蠢的情况,我知道),而UIAlertController可能出现在左上方(如2nd screenshot of the original question),我找到了一个单行解决方案,似乎工作 . 对于即将在UIAlertController上呈现的控制器,更改其模式表示样式,如下所示:

    viewControllerToBePresented.modalPresentationStyle = .OverFullScreen
    

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

  • 10

    这有点令人失望......将警报移动到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
    

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

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

  • 4

    我认为你应该像这样对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
    
  • 0

    我也有这个问题 . 如果我在呈现UIAlertController时呈现了一个视图控制器,则警报将转到左上角 .

    我的修复是刷新UIAlertController视图的中心viewDidLayoutSubviews;通过子类化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)
            }
        }
    }
    
  • 6

    除了Carl Lindberg's answer还有两种情况也应该考虑在内:

    • 设备旋转

    • 警报内有文本字段时的键盘高度

    那么,对我有用的完整答案:

    // 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
    
  • -2

    用户 manoj.agg 将此答案发布到了Open Radar bug report,但是说:

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

    在这里发布他的答案为后代 . 我没有测试/评估它 .


    Step 1:

    创建一个继承自 UIViewController 的自定义View Controller并实现 UIPopoverPresentationControllerDelegate

    @interface CustomUIViewController : UIViewController<UITextFieldDelegate, UIPopoverPresentationControllerDelegate>
    

    Step 2:

    全屏显示视图,使用演示文稿popover:

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

    我遇到了类似的问题,其中密码输入视图需要显示在任何其他视图控制器的顶部,包括UIAlertControllers . 上面的代码帮助我解决了这个问题 . iOS 8中值得注意的变化是 UIAlertController 继承自 UIViewController ,而 UIAlertView 则不然 .

  • 4
    var kbHeight: CGFloat = 0    
    
    override func keyboardWillShow(_ notification: Notification) {
        if let userInfo = notification.userInfo {
            if let keyboardSize =  (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
                kbHeight = keyboardSize.height
                self.animateTextField(up: true)
            }
        }
    }
    
    override func keyboardWillHide(_ notification: Notification) {
          self.animateTextField(up: false)
    }
    
    
    func animateTextField(up: Bool) {
        let movement = (up ? -kbHeight : kbHeight)
        UIView.animate(withDuration: 0.3, animations: {
            self.view.frame =  CGRect.offsetBy(self.view.frame)(dx: 0, dy: movement)
        })
    }
    

相关问题