web-dev-qa-db-ja.com

UIAlertControllerは、 `presentViewController:`を呼び出すと、画面上部のバグのある位置に移動します。

UIAlertControllerからのビューを表示すると、アラートが画面の左上隅のバグのある位置に移動します。 iOS 8.1、デバイスおよびシミュレーター。

現在の「一番上の」ビューからビューを表示しようとしたときに、アプリでこれに気づきました。 UIAlertControllerが最上位のビューである場合、この動作が発生します。 UIAlertControllersを単に無視するようにコードを変更しましたが、他の人が同じ問題に遭遇した場合に備えてこれを投稿しています(何も見つからなかったため)。

これを単純なテストプロジェクトに分離しました。完全なコードはこの質問の最後にあります。

  1. 新しいシングルビューXcodeプロジェクトのビューコントローラーにviewDidAppear:を実装します。
  2. A UIAlertControllerアラートを表示します。
  3. アラートコントローラーはすぐに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

39
pkamb

rdar:// 19037589 はAppleによってクローズされました

アップル開発者関係| 2015年2月25日10:52 AM

以下に基づいてこれに対処する計画はありません。

これはサポートされていません。UIAlertControllerでの提示は避けてください。

このレポートは現在クローズしています。

解決策について質問がある場合、またはこれが依然として重大な問題である場合は、バグレポートをその情報で更新してください。

この問題に影響する可能性のあるアップデートがないか、定期的に新しいApple=リリースを確認してください。

12
pkamb

時々モーダルビューがアラートの上に表示される状況(ばかげた状況、私は知っています)に遭遇し、UIAlertControllerが左上に表示される可能性があります( の2番目のスクリーンショット元の質問 )、そして私はうまくいくように見えるワンライナーソリューションを見つけました。 UIAlertControllerに表示されるコントローラーのモーダル表示スタイルを次のように変更します。

_viewControllerToBePresented.modalPresentationStyle = .OverFullScreen
_

これは、presentViewController(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion completion: (() -> Void)?)を呼び出す直前に行う必要があります。

15
senmu

私もこの問題を抱えていました。 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)
        }
    }
}
8
joels

これは少しがっかりです...アラートを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もカバーしますが、私はAppleが行うバグのある場所でのみスウィズルを使用することを好みます修正(したがって、スウィズルを削除できる時期があり、バグの回避策のためだけに既存のコードをマックアップせずに「そのまま」機能させることができます)。しかし、Appleが修正しないものである場合(ここでの別の回答に記載されているように、返信で示されています)、通常は動作を変更するために別のクラスを用意する方が安全であるため、既知の状況でのみ使用します。

6
Carl Lindberg

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
5
Pedro Ontiveros

Carl Lindbergの回答 に加えて、次の2つのケースも考慮する必要があります。

  1. デバイスの回転
  2. アラート内にテキストフィールドがある場合のキーボードの高さ

だから、私のために働いた完全な答え:

// 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
1
Andrew

ModalPresentationStyleを.OverFullScreenに設定しました

これでうまくいきました。

1
Asif

私はSwiftで同じ問題を扱っていましたが、これを変更して修正しました:

show(chooseEmailActionSheet!, sender: self) 

これに:

self.present(chooseEmailActionSheet!, animated: true, completion: nil) 
1
Gabo Ruiz

簡単な修正は、常に新しい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];
1
kaushal

ユーザーmanoj.aggOpen Radar bug report にこの回答を投稿しましたが、次のように述べています:

どういうわけか、Stackoverflowに回答を投稿するのに十分な評判がありません。

後世のために彼の答えをここに投稿します。私はそれをテスト/評価していません。


ステップ1:

UIViewControllerを継承するカスタムView Controllerを作成し、UIPopoverPresentationControllerDelegateを実装します。

@interface CustomUIViewController : UIViewController<UITextFieldDelegate, UIPopoverPresentationControllerDelegate>

ステップ2:

プレゼンテーションのポップオーバーを利用して、ビューをフルスクリーンで表示します。

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を含む他のView Controllerの上にパスワード入力ビューを表示する必要がありました。上記のコードは問題の解決に役立ちました。 iOS 8の注目すべき変更点は、UIAlertControllerUIViewControllerから継承することです。これはUIAlertViewには当てはまりませんでした。

0
pkamb