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?

typeoneerror
la source

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à.)

Kris Markel
la source
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]
samvermette
la source
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];
an0
la source
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).

lorean
la source
[[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.

hfossli
la source
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;
        }
    }
}
Miha Hribar
la source
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.

Harrison Xi
la source
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];
handiansom
la source
0

essaye ça

UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
Chandramani
la source
À 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;
        }
    }
}
user12775146
la source