UIAlertController is moved to buggy position at top of screen when it calls `presentViewController:`

前端 未结 12 1306
心在旅途
心在旅途 2020-12-08 06:36

Presenting a view from a UIAlertController moves the alert to a buggy position at the top-left corner of the screen. iOS 8.1, device and simulator.

We h

相关标签:
12条回答
  • 2020-12-08 07:15

    I set modalPresentationStyle to .OverFullScreen

    This worked for me.

    0 讨论(0)
  • 2020-12-08 07:17

    rdar://19037589 was closed by Apple

    Apple Developer Relations | 25-Feb-2015 10:52 AM

    There are no plans to address this based on the following:

    This isn't supported, please avoid presenting on a UIAlertController.

    We are now closing this report.

    If you have questions about the resolution, or if this is still a critical issue for you, then please update your bug report with that information.

    Please be sure to regularly check new Apple releases for any updates that might affect this issue.

    0 讨论(0)
  • 2020-12-08 07:18

    That is a bit disappointing... moving alerts to be UIViewControllers, but then disallowing some regular usage of them. I work on an application which did something similar -- it sometimes needs to jump to a new user context, and doing that presented a new view controller over top of whatever was there. Actually having the alerts be view controllers is almost better in this case, as they would be preserved. But we are seeing the same displacement now that we have switched to UIViewControllers.

    We may have to come up with a different solution (using different windows perhaps), and maybe we avoid presenting if the top level is a UIAlertController. But, it is possible to restore the correct positioning. It may not be a good idea, because the code could break if Apple ever changes their screen positioning, but the following subclass seems to work (in iOS8) if this functionality is very much needed.

    @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 may consider the view positioning to be private, so restoring it in this way may not be the best idea, but it works for now. It might be possible to store off the old frame in an override of -presentViewController:animated:, and simply restore that instead of re-calculating.

    It's possible to swizzle UIAlertController itself to do the equivalent of the above, which would also cover UIAlertControllers presented by code you don't control, but I prefer to only use swizzles in places where it's a bug that Apple is going to fix (thus there is a time when the swizzle can be removed, and we allow existing code to "just work" without mucking it up just for a bug workaround). But if it's something that Apple is not going to fix (indicated by their reply as noted in another answer here), then it's usually safer to have a separate class to modify behavior, so you are using it only in known circumstances.

    0 讨论(0)
  • 2020-12-08 07:25

    User manoj.agg posted this answer to the Open Radar bug report, but says:

    Somehow I don't have enough reputation to post answers on Stackoverflow.

    Posting his answer here for posterity. I have not tested/evaluated it.


    Step 1:

    Create a custom View Controller inheriting from UIViewController and implement UIPopoverPresentationControllerDelegate:

    @interface CustomUIViewController : UIViewController<UITextFieldDelegate, UIPopoverPresentationControllerDelegate>
    

    Step 2:

    Present the view in fullscreen, making use of the presentation 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];
        });
    }];
    

    I had a similar problem where a password input view needed to be displayed on top of any other View Controller, including UIAlertControllers. The above code helped me in solving the problem. Noteworthy change in iOS 8 is that UIAlertController inherits from UIViewController, which was not the case for UIAlertView.

    0 讨论(0)
  • 2020-12-08 07:26

    I was dealing with the same problem with swift, and I fixed it by changing this:

    show(chooseEmailActionSheet!, sender: self) 
    

    to this:

    self.present(chooseEmailActionSheet!, animated: true, completion: nil) 
    
    0 讨论(0)
  • 2020-12-08 07:29

    In addition to Carl Lindberg's answer There are two cases that also should be taken into account:

    1. Device rotating
    2. Keyboard height when there is a text field inside alert

    So, the full answer that worked for me:

    // 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;
            }
        }
    }
    

    Also, if using category, then you need to store keyboard height somehow, like this:

    @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
    
    0 讨论(0)
提交回复
热议问题