Obtenir une référence à la vue / fenêtre la plus haute dans l'application iOS


97

Je crée un cadre réutilisable pour afficher des notifications dans une application iOS. J'aimerais que les vues de notification soient ajoutées par-dessus tout le reste de l'application, un peu comme un UIAlertView. Lorsque j'initie le gestionnaire qui écoute les événements NSNotification et ajoute des vues en réponse, je dois obtenir une référence à la vue la plus haute de l'application. Voici ce que j'ai en ce moment:

_topView = [[[[UIApplication sharedApplication] keyWindow] subviews] lastObject];

Cela fonctionnerait-il pour n'importe quelle application iOS ou est-ce un moyen plus sûr / meilleur d'obtenir la vue de dessus?

Réponses:


36

Habituellement, cela vous donnera la vue de dessus, mais il n'y a aucune garantie qu'elle soit visible par l'utilisateur. Il peut être hors de l'écran, avoir un alpha de 0,0 ou avoir une taille de 0x0 par exemple.

Il se peut aussi que keyWindow n'ait pas de sous-vues, vous devriez donc probablement tester cela en premier. Ce serait inhabituel, mais ce n'est pas impossible.

UIWindow est une sous-classe de UIView, donc si vous voulez vous assurer que votre notification est visible pour l'utilisateur, vous pouvez l'ajouter directement à keyWindow en utilisant addSubview:et ce sera instantanément la vue la plus haute. Je ne sais pas si c'est ce que vous cherchez à faire. (D'après votre question, il semble que vous le sachiez déjà.)


111

Chaque fois que je veux afficher une superposition par-dessus tout le reste, je l'ajoute simplement en haut de la fenêtre d'application directement:

[[[UIApplication sharedApplication] keyWindow] addSubview:someView]

9
Ne fonctionne qu'en orientation portrait. Vous devez vous soucier des rotations, etc. comme ceci: stackoverflow.com/questions/2508630/…
hfossli

1
Je reçois (null)mon application iOS-8 (problème de storyboards?) Des indices? Merci! (Remarque: (null)renvoyé à la fois à viewDidLoadet à l' viewWillAppear:heure. Il viewDidAppear:est trop tard.
Olie

cela fonctionne-t-il lorsque nous présentons un contrôleur de vue?. La sous-vue ajoutée s'affichera-t-elle sur le contrôleur de vue présenté?
Pratyusha Terli

Cela n'ira pas au-dessus du clavier, mais lastObject le fait.
Ser Pounce

Ce cadre peut fonctionner dans toutes les orientations. Et ira au-dessus du clavier. github.com/HarrisonXi/TopmostView
Harrison Xi

47

Il y a deux parties du problème: fenêtre supérieure, vue de dessus sur la fenêtre supérieure.

Toutes les réponses existantes ont manqué la partie supérieure de la fenêtre. Mais il [[UIApplication sharedApplication] keyWindow]n'est pas garanti que ce soit la fenêtre du haut.

  1. Fenêtre du haut. Il est très peu probable qu'il y ait deux fenêtres avec la même windowLevelcoexistence pour une application, nous pouvons donc trier toutes les fenêtres windowLevelet obtenir la plus haute.

    UIWindow *topWindow = [[[UIApplication sharedApplication].windows sortedArrayUsingComparator:^NSComparisonResult(UIWindow *win1, UIWindow *win2) {
        return win1.windowLevel - win2.windowLevel;
    }] lastObject];
  2. Vue de dessus sur la fenêtre supérieure. Juste pour être complet. Comme déjà souligné dans la question:

    UIView *topView = [[topWindow subviews] lastObject];

5
Cette solution a cessé de fonctionner pour moi une fois que UIAlertViews est entré en jeu - ils semblent laisser une UIWindow vide, alors je suis revenu àtopView = [[[[UIApplication sharedApplication] keyWindow] subviews] lastObject];
Gereon

4
Cela ne fonctionne pas si le clavier est en place. Comme ça va être la fenêtre la plus haute
entropie

@Gereon, assurez-vous que vous ne détenez pas une référence forte à aucun UIAlertView. S'il ne s'agit pas d'une référence faible, l'UIAlertOverlayWindow sera toujours dans la hiérarchie et reconnue comme la fenêtre clé, mais les sous-vues qui y sont ajoutées ne s'afficheront pas.
beebcon

La valeur topWindow n'est pas correcte dans le cas d'iOS 8
Mehul Thakkar

11

En fait, il peut y avoir plusieurs UIWindow dans votre application. Par exemple, si un clavier est à l'écran, il [[UIApplication sharedApplication] windows]contiendra au moins deux fenêtres (votre fenêtre de touches et la fenêtre de clavier).

Donc, si vous voulez que votre vue apparaisse au-dessus des deux, vous devez faire quelque chose comme:

[[[[UIApplication sharedApplication] windows] lastObject] addSubview:view];

(En supposant que lastObject contient la fenêtre avec la priorité windowLevel la plus élevée).


[[UIApplication sharedApplication] windows] lastObject] valeur incorrecte dans le cas d'ios 8
Mehul Thakkar

J'ai commencé à l'utiliser, mais il semble que parfois la fenêtre du clavier existe mais ne s'affiche pas, puis ma vue ne s'affiche pas du tout!
teradyl

3

Je m'en tiens à la question comme l'indique le titre et non à la discussion. Quelle vue est visible en haut sur un point donné?

@implementation UIView (Extra)

- (UIView *)findTopMostViewForPoint:(CGPoint)point
{
    for(int i = self.subviews.count - 1; i >= 0; i--)
    {
        UIView *subview = [self.subviews objectAtIndex:i];
        if(!subview.hidden && CGRectContainsPoint(subview.frame, point))
        {
            CGPoint pointConverted = [self convertPoint:point toView:subview];
            return [subview findTopMostViewForPoint:pointConverted];
        }
    }

    return self;
}

- (UIWindow *)topmostWindow
{
    UIWindow *topWindow = [[[UIApplication sharedApplication].windows sortedArrayUsingComparator:^NSComparisonResult(UIWindow *win1, UIWindow *win2) {
        return win1.windowLevel - win2.windowLevel;
    }] lastObject];
    return topWindow;
}

@end

Peut être utilisé directement avec n'importe quelle UIWindow comme récepteur ou n'importe quelle UIView comme récepteur.


topWindow donne une valeur erronée dans le cas d'iOS 8, pouvez-vous s'il vous plaît mettre à jour votre réponse pour iOS 8
Mehul Thakkar

1
Je n'ai pas le temps de le savoir. Si vous le faites, vous êtes libre de mettre à jour ce message.
hfossli

1

Si vous ajoutez une vue de chargement (une vue d'indicateur d'activité par exemple), assurez-vous d'avoir un objet de la classe UIWindow. Si vous affichez une feuille d'action juste avant d'afficher votre vue de chargement, le keyWindowsera le UIActionSheetet non UIWindow. Et puisque la feuille d'action disparaîtra, la vue de chargement disparaîtra avec elle. Ou c'est ce qui me posait des problèmes.

UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
if (![NSStringFromClass([keyWindow class]) isEqualToString:@"UIWindow"]) {
    // find uiwindow in windows
    NSArray *windows = [UIApplication sharedApplication].windows;
    for (UIWindow *window in windows) {
        if ([NSStringFromClass([window class]) isEqualToString:@"UIWindow"]) {
            keyWindow = window;
            break;
        }
    }
}

1

Si votre application ne fonctionne qu'en orientation portrait, cela suffit:

[[[UIApplication sharedApplication] keyWindow] addSubview:yourView]

Et votre vue ne sera pas affichée sur le clavier et la barre d'état.

Si vous souhaitez obtenir une vue la plus haute sur le clavier ou la barre d'état, ou si vous voulez que la vue la plus haute puisse pivoter correctement avec les appareils, veuillez essayer ce cadre:

https://github.com/HarrisonXi/TopmostView

Il prend en charge iOS7 / 8/9.


0

Utilisez simplement ce code si vous souhaitez ajouter une vue ci-dessus de tout ce qui se trouve à l'écran.

[[UIApplication sharedApplication].keyWindow addSubView: yourView];

0

essaye ça

UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];

À propos de la propriété windows: "Cette propriété contient un tableau d'objets NSWindow correspondant à toutes les fenêtres existantes de l'application. Le tableau comprend toutes les fenêtres à l'écran et hors écran, qu'elles soient visibles ou non sur un espace. Il n'y a aucune garantie de l'ordre des fenêtres du tableau. "
Steven Fisher

0
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
if (![NSStringFromClass([keyWindow class]) isEqualToString:@"UIWindow"]) {

    NSArray *windows = [UIApplication sharedApplication].windows;
    for (UIWindow *window in windows) {
        if ([NSStringFromClass([window class]) isEqualToString:@"UIWindow"]) {
            keyWindow = window;
            break;
        }
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.