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 have noticed this in an app when we attempt to present a view from the current "top-most" view. If a UIAlertController happens to be the top-most view we get this behavior. We have changed our code to simply ignore UIAlertControllers, but I'm posting this in case others hit the same issue (as I couldn't find anything).
We have isolated this to a simple test project, full code at the bottom of this question.
- Implement
viewDidAppear:
on the View Controller in a new Single View Xcode project. - Present a
UIAlertController
alert. - Alert controller immediately calls
presentViewController:animated:completion:
to display and then dismiss another view controller:
The moment the presentViewController:...
animation begins, the UIAlertController is moved to the top-left corner of the screen:
When the dismissViewControllerAnimated:
animation ends, the alert has been moved even further into the top-left margin of the screen:
Full code:
- (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
}
Is there anything in the above code that would be causing this issue? Do any alternatives exist that would allow bug-free presentation of a view from a UIAlertController?
rdar://19037589
http://openradar.appspot.com/19037589
I set modalPresentationStyle to
.OverFullScreen
This worked for me.
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.
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.
I encountered a situation where sometimes a modal view would present itself on top of a an alert (silly situation, I know), and the UIAlertController could appear in the top left (like the 2nd screenshot of the original question), and I found a one-liner solution that seems to work. For the controller that's about to be presented on the UIAlertController, change its modal presentation style like so:
This should be done just before you call
presentViewController(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion completion: (() -> Void)?)
A quick fix is to always present the View Controller on top of a new UIWindow:
I was having this issue as well. If I presented a view controller while a UIAlertController was presented, the alert would go to the top left.
My fix is to refresh the center of the UIAlertController's view in viewDidLayoutSubviews; achieved by subclassing UIAlertController.