web-dev-qa-db-fra.com

UIAlertController est déplacé en position de buggy en haut de l'écran lorsqu'il appelle `presentViewController:`

La présentation d'une vue à partir d'un UIAlertController déplace l'alerte vers une position de buggy dans le coin supérieur gauche de l'écran. iOS 8.1, appareil et simulateur.

Nous l'avons remarqué dans une application lorsque nous tentons de présenter une vue à partir de la vue "la plus haute" actuelle. Si un UIAlertController se trouve être la vue la plus haute, nous obtenons ce comportement. Nous avons changé notre code pour ignorer simplement UIAlertControllers, mais je le poste au cas où d'autres rencontreraient le même problème (car je n'ai rien trouvé).

Nous l'avons isolé à un projet de test simple, le code complet au bas de cette question.

  1. Mettre en place viewDidAppear: sur le contrôleur de vue dans un nouveau projet Xcode Single View.
  2. Présentez une alerte UIAlertController.
  3. Le contrôleur d'alerte appelle immédiatement presentViewController:animated:completion: pour afficher puis fermer un autre contrôleur de vue:

Au moment où le presentViewController:... l'animation commence, le UIAlertController est déplacé dans le coin supérieur gauche de l'écran:

Alert has moved to top of screen

Quand le dismissViewControllerAnimated: l'animation se termine, l'alerte a été déplacée encore plus loin dans la marge supérieure gauche de l'écran:

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

Code complet:

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

Y a-t-il quelque chose dans le code ci-dessus qui pourrait causer ce problème? Existe-t-il des alternatives qui permettraient une présentation sans bogue d'une vue à partir d'un UIAlertController?

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

39
pkamb

rdar: // 19037589 a été fermé par Apple

Relations avec les développeurs Apple | 25 févr.2015 10h52

Il n'est pas prévu de résoudre ce problème sur la base des éléments suivants:

Cela n'est pas pris en charge, veuillez éviter de présenter sur un UIAlertController.

Nous clôturons maintenant ce rapport.

Si vous avez des questions sur la résolution, ou si c'est toujours un problème critique pour vous, veuillez mettre à jour votre rapport de bogue avec ces informations.

Veuillez vérifier régulièrement les nouvelles versions de Apple pour toutes les mises à jour susceptibles d'affecter ce problème.

12
pkamb

J'ai rencontré une situation où parfois une vue modale se présentait au-dessus d'une alerte (situation idiote, je sais), et l'UIAlertController pouvait apparaître en haut à gauche (comme le 2e capture d'écran de la question d'origine =), et j'ai trouvé une solution à un revêtement qui semble fonctionner. Pour le contrôleur qui est sur le point d'être présenté sur l'UIAlertController, changez son style de présentation modale comme suit:

viewControllerToBePresented.modalPresentationStyle = .OverFullScreen

Cela devrait être fait juste avant d'appeler presentViewController(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion completion: (() -> Void)?)

15
senmu

J'avais aussi ce problème. Si je présentais un contrôleur de vue alors qu'un UIAlertController était présenté, l'alerte irait en haut à gauche.

Ma solution consiste à actualiser le centre de la vue UIAlertController dans viewDidLayoutSubviews; obtenu en sous-classant 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

C'est un peu décevant ... déplacer les alertes pour qu'elles soient UIViewControllers, mais en interdisant une utilisation régulière. Je travaille sur une application qui a fait quelque chose de similaire - elle a parfois besoin de passer à un nouveau contexte utilisateur, et cela a présenté un nouveau contrôleur de vue par-dessus tout ce qui s'y trouvait. En fait, avoir les alertes comme contrôleurs de vue est presque mieux dans ce cas, car ils seraient préservés. Mais nous constatons le même déplacement maintenant que nous sommes passés à UIViewControllers.

Nous devrons peut-être trouver une solution différente (en utilisant peut-être des fenêtres différentes), et peut-être éviterons-nous de présenter si le niveau supérieur est un UIAlertController. Mais, il est possible de restaurer le bon positionnement. Ce n'est peut-être pas une bonne idée, car le code pourrait se casser si Apple change un jour le positionnement de son écran, mais la sous-classe suivante semble fonctionner (dans iOS8) si cette fonctionnalité est vraiment nécessaire.

@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 peut considérer que le positionnement de la vue est privé, donc le restaurer de cette manière n'est peut-être pas la meilleure idée, mais cela fonctionne pour l'instant. Il peut être possible de stocker l'ancienne image dans un remplacement de -presentViewController: animated:, et de simplement restaurer cela au lieu de recalculer.

Il est possible de swizzle UIAlertController lui-même pour faire l'équivalent de ce qui précède, ce qui couvrirait également UIAlertControllers présenté par du code que vous ne contrôlez pas, mais je préfère n'utiliser des swizzles que dans les endroits où c'est un bogue que Apple va correction (il y a donc un moment où le swizzle peut être supprimé, et nous permettons au code existant de "fonctionner" sans le nettoyer juste pour une solution de contournement de bogue). Mais si c'est quelque chose que Apple ne va pas résoudre (indiqué par leur réponse comme indiqué dans une autre réponse ici), il est généralement plus sûr d'avoir une classe distincte pour modifier le comportement, donc vous ne l'utilisez que dans des circonstances connues .

6
Carl Lindberg

Je pense que vous devez uniquement catégoriser UIAlertController comme ceci:

@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

En plus de réponse de Carl Lindberg Il y a deux cas qui doivent également être pris en compte:

  1. Dispositif tournant
  2. Hauteur du clavier lorsqu'il y a un champ de texte dans l'alerte

Donc, la réponse complète qui a fonctionné pour moi:

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

De plus, si vous utilisez une catégorie, vous devez en quelque sorte stocker la hauteur du clavier, comme ceci:

@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

J'ai défini modalPresentationStyle sur .OverFullScreen

Cela a fonctionné pour moi.

1
Asif

Je faisais face au même problème avec Swift, et je l'ai résolu en changeant ceci:

show(chooseEmailActionSheet!, sender: self) 

pour ça:

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

Une solution rapide consiste à toujours présenter le contrôleur de vue au-dessus d'une nouvelle fenêtre d'interface utilisateur:

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

Utilisateur manoj.agg a posté cette réponse dans le Rapport de bogue d'Open Radar , mais dit:

D'une certaine manière, je n'ai pas assez de réputation pour publier des réponses sur Stackoverflow.

Poster sa réponse ici pour la postérité. Je ne l'ai pas testé/évalué.


Étape 1:

Créez un contrôleur de vue personnalisé héritant de UIViewController et implémentez UIPopoverPresentationControllerDelegate:

@interface CustomUIViewController : UIViewController<UITextFieldDelegate, UIPopoverPresentationControllerDelegate>

Étape 2:

Présentez la vue en plein écran, en utilisant le popover de présentation:

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

J'ai eu un problème similaire où une vue d'entrée de mot de passe devait être affichée au-dessus de tout autre contrôleur de vue, y compris UIAlertControllers. Le code ci-dessus m'a aidé à résoudre le problème. Un changement notable dans iOS 8 est que UIAlertController hérite de UIViewController, ce qui n'était pas le cas pour UIAlertView.

0
pkamb