97

I'm creating a reusable framework for displaying notifications in an iOS application. I'd like the notification views to be added over the top of everything else in the application, sort of like a UIAlertView. When I init the manager that listens for NSNotification events and adds views in response, I need to get a reference to the top-most view in the application. This is what I have at the moment:

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

Would this work for any iOS application or is their a safer/better way to get the top view?

typeoneerror
  • 55,990
  • 32
  • 132
  • 223

10 Answers10

113

Whenever I want to display some overlay on top of everything else, I just add it on top of the Application Window directly:

[[[UIApplication sharedApplication] keyWindow] addSubview:someView]
samvermette
  • 40,269
  • 27
  • 112
  • 144
  • 9
    Only works in portrait orientation.. You gotta care about rotations etc like this: http://stackoverflow.com/questions/2508630/orientation-in-a-uiview-added-to-a-uiwindow/4960988#4960988 – hfossli Feb 06 '13 at 18:26
  • 2
    I'm getting `(null)` in my iOS-8 app (storyboards issue?) Any hints? Thanks! (Note: `(null)` returned both at `viewDidLoad` and `viewWillAppear:` time. `viewDidAppear:` is too late. – Olie Feb 26 '15 at 18:29
  • does this work when we present a view controller?. Will the added subview shown over the presented view controller? – Pratyusha Terli Mar 24 '15 at 14:56
  • This won't go above the keyboard, lastObject does though. – Ser Pounce May 12 '15 at 23:08
  • This framework can work in all orientations. And will go above keyboard. https://github.com/HarrisonXi/TopmostView – Harrison Xi Feb 25 '16 at 03:19
47

There are two parts of the problem: Top window, top view on top window.

All the existing answers missed the top window part. But [[UIApplication sharedApplication] keyWindow] is not guaranteed to be the top window.

  1. Top window. It is very unlikely that there will be two windows with the same windowLevel coexist for an app, so we can sort all the windows by windowLevel and get the topmost one.

    UIWindow *topWindow = [[[UIApplication sharedApplication].windows sortedArrayUsingComparator:^NSComparisonResult(UIWindow *win1, UIWindow *win2) {
        return win1.windowLevel - win2.windowLevel;
    }] lastObject];
    
  2. Top view on top window. Just to be complete. As already pointed out in the question:

    UIView *topView = [[topWindow subviews] lastObject];
    
Besi
  • 22,579
  • 24
  • 131
  • 223
an0
  • 17,191
  • 12
  • 86
  • 136
  • 5
    This solution stopped working for me once UIAlertViews came into play - they seem to leave an emtpy UIWindow behind, so I reverted back to `topView = [[[[UIApplication sharedApplication] keyWindow] subviews] lastObject];` – Gereon Jan 05 '13 at 15:48
  • 4
    This doesn't work if the keyboard is up. As that's gonna be the topmost window – entropy Sep 10 '13 at 13:17
  • @Gereon, make sure you're not holding a strong reference to any UIAlertView. If it isn't a weak reference, the UIAlertOverlayWindow will still be in the hierarchy and recognized as the key window, but subviews added to it won't display. – beebcon Jul 03 '14 at 20:34
  • topWindow value is not proper in case of iOS 8 – Mehul Thakkar Dec 01 '14 at 11:16
36

Usually that will give you the top view, but there's no guarantee that it's visible to the user. It could be off the screen, have an alpha of 0.0, or could be have size of 0x0 for example.

It could also be that the keyWindow has no subviews, so you should probably test for that first. This would be unusual, but it's not impossible.

UIWindow is a subclass of UIView, so if you want to make sure your notification is visible to the user, you can add it directly to the keyWindow using addSubview: and it will instantly be the top most view. I'm not sure if this is what you're looking to do though. (Based on your question, it looks like you already know this.)

Kris Markel
  • 12,142
  • 3
  • 43
  • 40
11

Actually there could be more than one UIWindow in your application. For example, if a keyboard is on screen then [[UIApplication sharedApplication] windows] will contain at least two windows (your key-window and the keyboard window).

So if you want your view to appear ontop of both of them then you gotta do something like:

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

(Assuming lastObject contains the window with the highest windowLevel priority).

Besi
  • 22,579
  • 24
  • 131
  • 223
lorean
  • 2,150
  • 19
  • 25
  • [[[UIApplication sharedApplication] windows] lastObject] value not proper in case of ios 8 – Mehul Thakkar Dec 01 '14 at 11:16
  • I started using this, but it seems sometimes the keyboard window exists but isn't showing, and then my view doesn't display at all! – teradyl Jun 20 '16 at 23:47
3

I'm sticking to the question as the title states and not the discussion. Which view is top visible on any given point?

@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

Can be used directly with any UIWindow as receiver or any UIView as receiver.

hfossli
  • 22,616
  • 10
  • 116
  • 130
1

If you are adding a loading view (an activity indicator view for instance), make sure you have an object of UIWindow class. If you show an action sheet just before you show your loading view, the keyWindow will be the UIActionSheet and not UIWindow. And since the action sheet will go away, the loading view will go away with it. Or that's what was causing me problems.

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
  • 5,776
  • 3
  • 26
  • 24
1

If your application only works in portrait orientation, this is enough:

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

And your view will not be shown over keyboard and status bar.

If you want to get a topmost view that over keyboard or status bar, or you want the topmost view can rotate correctly with devices, please try this framework:

https://github.com/HarrisonXi/TopmostView

It supports iOS7/8/9.

Harrison Xi
  • 766
  • 1
  • 5
  • 23
0

try this

UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
Dhara
  • 4,093
  • 2
  • 36
  • 69
Chandramani
  • 871
  • 1
  • 12
  • 11
  • About the windows property: "This property contains an array of NSWindow objects corresponding to all currently existing windows for the app. The array includes all onscreen and offscreen windows, whether or not they are visible on any space. There is no guarantee of the order of the windows in the array." – Steven Fisher Mar 26 '18 at 22:28
0

Just use this code if you want to add a view above of everything in the screen.

[[UIApplication sharedApplication].keyWindow addSubView: yourView];
handiansom
  • 783
  • 11
  • 27
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;
        }
    }
}