Получение ссылки на самый верхний вид / окно в приложении iOS

97

Я создаю многоразовую структуру для отображения уведомлений в приложении iOS. Я бы хотел, чтобы представления уведомлений были добавлены поверх всего остального в приложении, вроде как UIAlertView. Когда я запускаю диспетчер, который прослушивает события NSNotification и добавляет представления в ответ, мне нужно получить ссылку на самое верхнее представление в приложении. Вот что у меня есть на данный момент:

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

Будет ли это работать для любого приложения iOS или это более безопасный / лучший способ получить вид сверху?

typeoneerror
источник

Ответы:

36

Обычно это дает вам вид сверху, но нет гарантии, что он будет виден пользователю. Он может быть за пределами экрана, иметь значение альфа 0,0 или, например, иметь размер 0x0.

Также может быть, что keyWindow не имеет подвидов, поэтому вам, вероятно, следует сначала проверить это. Это было бы необычно, но возможно.

UIWindow является подклассом UIView, поэтому, если вы хотите, чтобы ваше уведомление было видимым для пользователя, вы можете добавить его непосредственно в keyWindow с помощью, addSubview:и оно мгновенно станет самым верхним представлением. Я не уверен, что вы собираетесь делать именно это. (Судя по вашему вопросу, похоже, вы это уже знаете.)

Крис Маркел
источник
111

Всякий раз, когда я хочу отобразить какой-либо оверлей поверх всего остального, я просто добавляю его непосредственно поверх окна приложения:

[[[UIApplication sharedApplication] keyWindow] addSubview:someView]
Самвермет
источник
9
Работает только в портретной ориентации .. Вы должны позаботиться о поворотах и ​​т. Д., Например: stackoverflow.com/questions/2508630/…
hfossli 06
1
Я получаю (null)приложение для iOS-8 (проблема с раскадровкой?) Есть подсказки? Спасибо! (Примечание: (null)возвращено одновременно viewDidLoadи viewWillAppear:вовремя. viewDidAppear:Слишком поздно.
Оли,
работает ли это, когда мы представляем контроллер представления ?. Будет ли добавленное представление отображаться поверх представленного контроллера представления?
Пратюша Терли,
Это не будет выходить за рамки клавиатуры, хотя lastObject делает.
Ser Pounce
Эта структура может работать во всех ориентациях. И пойдет над клавиатурой. github.com/HarrisonXi/TopmostView
Харрисон Си
47

Проблема состоит из двух частей: верхнее окно, вид сверху на верхнее окно.

Все существующие ответы не попали в верхнюю часть окна. Но [[UIApplication sharedApplication] keyWindow]не гарантируется, что будет верхнее окно.

  1. Верхнее окно. Очень маловероятно, что windowLevelдля приложения будет два окна с одинаковым сосуществованием, поэтому мы можем отсортировать все окна windowLevelи получить самое верхнее.

    UIWindow *topWindow = [[[UIApplication sharedApplication].windows sortedArrayUsingComparator:^NSComparisonResult(UIWindow *win1, UIWindow *win2) {
        return win1.windowLevel - win2.windowLevel;
    }] lastObject];
    
  2. Вид сверху на верхнее окно. Просто чтобы быть полным. Как уже указывалось в вопросе:

    UIView *topView = [[topWindow subviews] lastObject];
an0
источник
5
Это решение перестало работать для меня, как только UIAlertViews вошел в игру - они, похоже, оставляют пустое UIWindow, поэтому я вернулся кtopView = [[[[UIApplication sharedApplication] keyWindow] subviews] lastObject];
Gereon
4
Это не работает, если клавиатура поднята. Поскольку это будет самое верхнее окно
энтропия
@Gereon, убедитесь, что у вас нет сильной ссылки на какой-либо UIAlertView. Если это не слабая ссылка, UIAlertOverlayWindow по-прежнему будет находиться в иерархии и распознаваться как ключевое окно, но добавленные к нему подпредставления не будут отображаться.
beebcon
Значение topWindow неверно в случае iOS 8
Мехул Таккар 01
11

На самом деле в вашем приложении может быть более одного UIWindow. Например, если на экране отображается клавиатура, тогда [[UIApplication sharedApplication] windows]будет как минимум два окна (ваше ключевое окно и окно клавиатуры).

Поэтому, если вы хотите, чтобы ваше представление отображалось поверх них обоих, вам нужно сделать что-то вроде:

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

(Предполагается, что lastObject содержит окно с наивысшим приоритетом windowLevel).

лорианец
источник
[[[UIApplication sharedApplication] windows] lastObject] значение неверно в случае ios 8
Мехул Таккар 01
Я начал использовать это, но иногда кажется, что окно клавиатуры существует, но не отображается, а затем мое представление вообще не отображается!
teradyl
3

Я придерживаюсь вопроса в названии, а не обсуждения. Какой вид лучше всего виден в любой точке?

@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

Может использоваться напрямую с любым UIWindow как получателем или любым UIView как получателем.

Hfossli
источник
topWindow дает неправильное значение в случае iOS 8, не могли бы вы обновить свой ответ для iOS 8
Мехул Таккар,
1
У меня нет времени узнавать. Если да, то можете обновить этот пост.
hfossli 01
1

Если вы добавляете представление загрузки (например, представление индикатора активности), убедитесь, что у вас есть объект класса UIWindow. Если вы показываете лист действий непосредственно перед тем, как вы показываете вид загрузки, то keyWindowбудет, UIActionSheetа не UIWindow. И так как лист действий исчезнет, ​​представление загрузки исчезнет вместе с ним. Или вот что вызывало у меня проблемы.

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

Если ваше приложение работает только в портретной ориентации, этого достаточно:

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

И ваше представление не будет отображаться над клавиатурой и строкой состояния.

Если вы хотите получить самый верхний вид, который находится над клавиатурой или строкой состояния, или вы хотите, чтобы самый верхний вид мог правильно вращаться с устройствами, попробуйте эту структуру:

https://github.com/HarrisonXi/TopmostView

Он поддерживает iOS7 / 8/9.

Харрисон Си
источник
0

Просто используйте этот код, если хотите добавить вид сверху всего на экране.

[[UIApplication sharedApplication].keyWindow addSubView: yourView];
хэнди
источник
0

попробуй это

UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
Чандрамани
источник
О свойстве windows: «Это свойство содержит массив объектов NSWindow, соответствующих всем существующим в данный момент окнам приложения. Массив включает в себя все экранные и закадровые окна, независимо от того, видны ли они в каком-либо пространстве. Нет никакой гарантии порядка окон в массиве ".
Стивен Фишер,
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
источник