从UIAlertController
呈现视图会将警报移动到屏幕左上角的错误位置。 iOS 8.1,设备和模拟器。
当我们尝试从当前“最顶层”视图中呈现视图时,我们在应用程序中注意到了这一点。如果UIAlertController恰好是最顶层的视图,我们会得到这种行为。我们已经更改了我们的代码以简单地忽略UIAlertControllers,但我发布这个以防其他人遇到同样的问题(因为我找不到任何东西)。
我们已将此与一个简单的测试项目隔离开来,这个问题的底部是完整的代码。
viewDidAppear:
。UIAlertController
提醒。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
}
上述代码中是否有任何内容会导致此问题?是否存在允许从UIAlertController无错误地呈现视图的替代方法?
rdar:// 19037589个
http://openradar.appspot.com/19037589
答案 0 :(得分:11)
rdar://19037589已被Apple关闭
Apple开发者关系| 25-Feb-2015 10:52 AM
没有计划根据以下内容解决此问题:
这不受支持,请避免在UIAlertController上展示。
我们现在关闭此报告。
如果您对该决议有疑问,或者这对您来说仍然是一个关键问题,请使用该信息更新您的错误报告。
请务必定期检查新的Apple版本是否有可能影响此问题的任何更新。
答案 1 :(得分:11)
我遇到过一种情况,有时模态视图会出现在警报之上(愚蠢的情况,我知道),而UIAlertController可能出现在左上角(如2nd screenshot of the original question),而我找到了一个似乎有效的单线解决方案。对于即将在UIAlertController上呈现的控制器,更改其模态表示样式如下:
viewControllerToBePresented.modalPresentationStyle = .OverFullScreen
这应该在您致电presentViewController(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion completion: (() -> Void)?)
答案 2 :(得分: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
Apple可能认为视图定位是私有的,因此以这种方式恢复它可能不是最好的主意,但它现在有效。也许可以在-presentViewController:animated:的覆盖中存储旧框架,并简单地恢复它而不是重新计算。
可以调整UIAlertController本身来完成上述操作,这也包括你无法控制的代码所呈现的UIAlertControllers,但我更喜欢只在那些苹果要修复的bug的地方使用swizzles (因此有一段时间可以删除混合体,并且我们允许现有代码“正常工作”而不会仅仅为了解决bug问题)。但是,如果这是Apple不会修复的东西(如他们的回复所示,如此处的另一个答案所述),那么通常有一个单独的类来修改行为更安全,所以你只在已知情况下使用它。
答案 3 :(得分: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
答案 4 :(得分:4)
我也有这个问题。如果我在呈现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)
}
}
}
答案 5 :(得分:1)
除了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
答案 6 :(得分:1)
一个快速解决方案是始终将视图控制器显示在新的UIWindow之上:
UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
window.rootViewController = [[UIViewController alloc] init];
window.windowLevel = UIWindowLevelNormal;
[window makeKeyAndVisible];
[window.rootViewController presentViewController: viewController
animated:YES
completion:nil];
答案 7 :(得分:1)
我当时正迅速处理相同的问题,并通过更改此方法解决了该问题:
show(chooseEmailActionSheet!, sender: self)
对此:
self.present(chooseEmailActionSheet!, animated: true, completion: nil)
答案 8 :(得分:1)
我将modalPresentationStyle设置为.OverFullScreen
这对我有用。
答案 9 :(得分:1)
编辑:在2020年测试,Xcode 11.2,iOS 13
如果仍然有人在寻找更好的答案,那么这就是我的解决方案。
使用updateConstraints
方法重新调整约束。
your_alert_controller_obj.updateConstraints()
答案 10 :(得分:0)
用户manoj.agg
将此答案发布到Open Radar bug report,但说:
不知何故,我没有足够的声誉在Stackoverflow上发布答案。
在这里为后人发布他的答案。我没有测试/评估它。
第1步:
创建一个继承自UIViewController
的自定义View Controller并实施UIPopoverPresentationControllerDelegate
:
@interface CustomUIViewController : UIViewController<UITextFieldDelegate, UIPopoverPresentationControllerDelegate>
第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
并非如此。
答案 11 :(得分:-2)
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)
})
}