Контроллер презентации iOS 8 определяет, действительно ли это всплывающее окно

Я использую новую адаптивную функцию «Present As Popover» в iOS 8. Я подключил простой переход в StoryBoard, чтобы сделать презентацию. Он отлично работает на iPhone 6 Plus, поскольку представляет вид в виде всплывающего окна, а на iPhone 4s — в полноэкранном режиме (стиль листа).

Проблема заключается в том, что при отображении в полноэкранном режиме мне нужно добавить кнопку «Готово» в представление, чтобы можно было вызвать команду rejectViewControllerAnimated. И я не хочу показывать кнопку «Готово», когда она отображается как всплывающее окно.

введите здесь описание изображения

Я попытался просмотреть свойства как PresentationController, так и popoverPresentationController, и я не могу найти ничего, что говорило бы мне, действительно ли оно отображается как всплывающее окно.

NSLog( @"View loaded %lx", (long)self.presentationController.adaptivePresentationStyle );          // UIModalPresentationFullScreen
NSLog( @"View loaded %lx", (long)self.presentationController.presentationStyle );                  // UIModalPresentationPopover
NSLog( @"View loaded %lx", (long)self.popoverPresentationController.adaptivePresentationStyle );   // UIModalPresentationFullScreen
NSLog( @"View loaded %lx", (long)self.popoverPresentationController.presentationStyle );           // UIModalPresentationPopover

AdaptivePresentationStyle всегда возвращает UIModalPresentationFullScreen, а PresentationStyle всегда возвращает UIModalPresentationPopover.

При просмотре коллекции UITraitCollection я нашел черту под названием «_UITraitNameInteractionModel», которая была установлена ​​в 1 только тогда, когда она фактически отображалась как всплывающее окно. Однако Apple не предоставляет прямого доступа к этому признаку через traitCollection popoverPresentationController.


person Tod Cunningham    schedule 01.11.2014    source источник
comment
Вы уже нашли решение?   -  person Bruce    schedule 02.08.2015
comment
Я думаю, что ответ Роба Гласси является наиболее полным. Однако Apple должна предоставить гораздо более простой способ сделать это.   -  person Tod Cunningham    schedule 27.09.2015
comment
@TodCunningham Apple предлагает простой способ, см. мой ответ.   -  person malhal    schedule 01.03.2016
comment
Извините, @malhal, но мое приложение — это игра, и я не хотел добавлять навигационный контроллер только для того, чтобы иметь кнопку «Готово». Не соответствует стилю игры.   -  person Tod Cunningham    schedule 11.07.2016


Ответы (8)


Лучший способ (наименее вонючий), который я нашел, это использовать UIPopoverPresentationControllerDelegate.

• Убедитесь, что представленный контроллер представления установлен как UIPopoverPresentationControllerDelegate на UIPopoverPresentationController, используемом для управления презентацией. Я использую раскадровку, поэтому установите это в prepareForSegue:

segue.destinationViewController.popoverPresentationController.delegate = presentedVC;

• Создайте свойство в представленном контроллере представления, чтобы отслеживать это состояние:

@property (nonatomic, assign) BOOL amDisplayedInAPopover;

• И добавьте следующий метод делегата (или добавьте к существующему методу делегата):

- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
    // This method is only called if we are presented in a popover
    self.amDisplayedInAPopover = YES;
}

• И, наконец, в viewWillAppear: - viewDidLoad: слишком рано, метод подготовки делегата вызывается между viewDidLoad: и viewWillAppear:

if (self.amDisplayedInAPopover) {
    // Hide the offending buttons in whatever manner you do so
    self.navigationItem.leftBarButtonItem = nil;
}

Изменить: более простой метод!

Просто установите делегата (убедитесь, что ваш представленный VC принимает UIPopoverPresentationControllerDelegate):

segue.destinationViewController.popoverPresentationController.delegate = presentedVC;

И укажите метод:

- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
    // This method is only called if we are presented in a popover
    // Hide the offending buttons in whatever manner you do so
    self.navigationItem.leftBarButtonItem = nil;
}
person Rob Glassey    schedule 22.07.2015
comment
Как получить ссылку на представленный VC внутри prepareForSegue? - person Bruce; 02.08.2015
comment
segue.destinationViewController — это место, с которого можно начать поиск. Будьте осторожны, если у вас есть промежуточный навигационный контроллер перед контроллером представления, который, как вы думаете, вы представляете, поскольку конечным VC будет навигационный контроллер. - person Rob Glassey; 02.08.2015
comment
Это как раз мой случай. У меня есть промежуточный навигационный контроллер, поэтому я в конечном итоге ссылаюсь на свой представленный VC как таковой: segue.destinationViewController.topViewController как? UIPopoverPresentationControllerDelegate, и кажется, что он работает... правильно ли он выглядит? - person Bruce; 02.08.2015
comment
Пробовал все другие решения здесь, но это единственное, которое работает и имеет наименьший запах. Спасибо! - person Bruce; 02.08.2015
comment
Да, topViewController на самом деле выглядит намного лучше, чем то, что я использовал в прошлом! (вмешательствоNavigationController.viewControllers.lastObject). - person Rob Glassey; 02.08.2015
comment
Вы также можете просто скрыть кнопки в методе делегата. Нет необходимости отслеживать состояние или изменять viewWillAppear:, если только нет шанса, что тот же самый экземпляр представленного VC будет снова представлен вне всплывающего окна (очень маловероятно, если вы не кэшируете его и не используете повторно). - person Rob Glassey; 02.08.2015
comment
Обратите внимание, что это не работает с классами адаптивного размера. prepareForPopoverPresentation вызывается каждый раз, когда полноэкранный режим становится всплывающим окном, но не наоборот. - person Frederik Winkelsdorf; 27.03.2016

Я проверяю, установлено ли значение arrowDirection popoverPresentationController после размещения представления. Для моих целей это работает достаточно хорошо и охватывает случай всплывающих окон на устройствах с меньшим экраном.

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)

    if (popoverPresentationController?.arrowDirection != UIPopoverArrowDirection.Unknown) {
        // This view controller is running in a popover
        NSLog("I'm running in a Popover")
    }
}
person John E. Ray    schedule 14.12.2014
comment
Вы нашли лучший способ, так как этот ответ был написан? - person SAHM; 16.03.2015

Как насчет

if (self.modalPresentationStyle == UIModalPresentationPopover)

Это работает для меня

person Scroot    schedule 17.05.2015

Я протестировал все решения, представленные в этом посте. К сожалению, ни один из них не работает правильно во всех случаях. Например, в iPad стиль презентации с разделенным видом может меняться при перетаскивании линии разделенного вида, поэтому для этого нам нужно специальное уведомление. После нескольких часов исследований я нашел решение в образце яблока (быстро): https://developer.apple.com/library/ios/samplecode/AdaptivePhotos/Introduction/Intro.html#//apple_ref/doc/uid/TP40014636

Вот такое же решение в obj-c.

Сначала в функции prepareForSegue установите делегат popoverPresentationController. Его также можно установить в MyViewController "init", но не в "viewDidLoad" (поскольку сначала вызывается willPresentWithAdaptiveStyle до viewDidLoad).

MyViewController *controller = [segue destinationViewController];
        controller.popoverPresentationController.delegate = (MyViewController *)controller;

Теперь объект MyViewController будет получать это уведомление каждый раз, когда iOS меняет стиль представления, включая первое представление. Вот пример реализации, которая показывает/скрывает кнопку "Закрыть" в navigationController:

- (void)presentationController:(UIPresentationController *)presentationController
  willPresentWithAdaptiveStyle:(UIModalPresentationStyle)style
         transitionCoordinator:(nullable id<UIViewControllerTransitionCoordinator>)transitionCoordinator {
    if (style == UIModalPresentationNone) {
        // style set in storyboard not changed (popover), hide close button
        self.topViewController.navigationItem.leftBarButtonItem = nil;
    } else {
        // style changed by iOS (to fullscreen or page sheet), show close button
        UIBarButtonItem *closeButton =
            [[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStylePlain target:self action:@selector(closeAction)];
        self.topViewController.navigationItem.leftBarButtonItem = closeButton;
    }
}

- (void)closeAction {
    [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
person Slyv    schedule 12.05.2016

UIPresentationController, который управляет вашим контроллером представления, представляет его, установив modalPresentationStyle в UIModalPresentationPopover.

Согласно UIViewController справке:

представление ViewController

  • Контроллер представления, который представил этот контроллер представления. (только для чтения)

modalPresentationStyle

  • UIModalPresentationPopover: в горизонтальной регулярной среде стиль представления, при котором содержимое отображается во всплывающем окне. Фоновое содержимое затемнено, а касание за пределами всплывающего окна приводит к его закрытию. Если вы не хотите, чтобы касания закрывали всплывающее окно, вы можете назначить одно или несколько представлений свойству passthroughViews связанного объекта UIPopoverPresentationController, которое можно получить из свойства popoverPresentationController.

Поэтому мы можем определить, находится ли ваш контроллер представления внутри всплывающего окна или представлен модально, проверив horizontalSizeClass следующим образом (я предположил, что ваша кнопка - это UIBarButtonItem)

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    if (self.presentingViewController.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular)
        self.navigationItem.leftBarButtonItem = nil; // remove the button
}

Безопаснее всего проверить это в viewWillAppear:, иначе presentingViewController может быть nil.

person Tomas Camin    schedule 20.11.2014
comment
Вы не можете сделать вывод из класса горизонтального размера, находитесь ли вы во всплывающем окне или нет. Контроллер представления на iPhone также имеет обычный класс горизонтального размера. - person Frédéric Adda; 24.11.2014
comment
@ФредА. посмотрите WWDC 2014 Session 216 @4:24. iPhone (кроме iPhone 6Plus) имеют классы компактных размеров как в горизонтальной, так и в вертикальной ориентации. Опубликованное решение отлично работает в одном из моих проектов, где я использую адаптивный переход Present as Popover для представления контроллера popover. Разве это не работает для вас? - person Tomas Camin; 25.11.2014
comment
Это может сработать, но это то же самое, что проверить, что userInterfaceIdiom — это iPad. Кроме того, вы можете принудительно отображать содержимое во всплывающем окне даже на iPhone. Поэтому я думаю, что вопрос заключается в том, чтобы конкретно проверить, представлен ли контент во всплывающем окне. - person Frédéric Adda; 25.11.2014
comment
Нет, это не будет работать на iPhone 6Plus в альбомной ориентации. При использовании адаптивного перехода Present as Popover всплывающие окна будут представлены как всплывающие окна или модально в зависимости от класса горизонтального размера, как указано в цитируемых документах. - person Tomas Camin; 25.11.2014
comment
Я понимаю вашу точку зрения. Если у вас есть всплывающее окно в компактном горизонтальном размере, оно будет представлено в виде модального полноэкранного режима. Это поведение по умолчанию. Теперь проверьте iOS 8 Рэя Вендерлиха по главе 6 учебника, вы увидите, что теперь возможно (и совершенно законно) принудительно использовать всплывающие окна даже на iPhone в портретной ориентации. В этом случае, как вы узнаете, что вам нужно удалить элемент кнопки панели? Я просто говорю, что решение, которое вы предлагаете, слишком сильно зависит от классов размеров, а не от проверки того, что мы действительно находимся в поповере. - person Frédéric Adda; 25.11.2014

Официальный способ реализовать это — сначала удалить кнопку «Готово» из контроллера представления, а во-вторых, при адаптации для компактного встраивания контроллера представления в контроллер навигации добавить кнопку «Готово» в качестве элемента навигации:

func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
    return UIModalPresentationStyle.FullScreen
}

func presentationController(controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
    let navigationController = UINavigationController(rootViewController: controller.presentedViewController)
    let btnDone = UIBarButtonItem(title: "Done", style: .Done, target: self, action: "dismiss")
    navigationController.topViewController.navigationItem.rightBarButtonItem = btnDone
    return navigationController
}

func dismiss() {
    self.dismissViewControllerAnimated(true, completion: nil)
}

Полное руководство

Скриншоты

person malhal    schedule 01.03.2016

Решение, работающее с многозадачностью

Назначьте представляющий контроллер делегатом всплывающего окна.

...
controller.popoverPresentationController.delegate = controller;
[self presentViewController:controller animated:YES completion:nil];

Затем в контроллере реализуем методы делегата:

- (void)presentationController:(UIPresentationController *)presentationController willPresentWithAdaptiveStyle:(UIModalPresentationStyle)style transitionCoordinator:(id<UIViewControllerTransitionCoordinator>)transitionCoordinator
{
    if (style != UIModalPresentationNone)
    {
        // Exited popover mode
        self.navigationItem.leftBarButtonItem = button;
    }
}

- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
    // Entered popover mode
    self.navigationItem.leftBarButtonItem = nil;
}
person beebcon    schedule 06.10.2016

Мое хитрое решение работает отлично.

В PopoverViewController viewDidLoad.

if (self.view.superview!.bounds != UIScreen.main.bounds) {
    print("This is a popover!")
}

Идея проста: размер представления всплывающего окна никогда не равен размеру экрана устройства, если только это не всплывающее окно.

person Alice Chan    schedule 30.11.2017
comment
Это отлично сработало для меня, но мне пришлось выполнить проверку в viewDidAppear. - person Chuck Boris; 14.12.2017
comment
В моем случае мне нужно было проверить границы представления вместо границ суперпредставления и проверить его в viewWillAppear или более поздней версии. Цель-C: bool isFullScreen = CGRectEqualToRect(self.view.bounds, [UIScreen mainScreen].bounds); - person jk7; 17.08.2018