Представляете модальный контроллер без знания текущего контроллера представления?

Есть ли способ представить контроллер представления модально, не зная, что такое представление контроллера видимого представления? По сути, это похоже на то, как если бы вы показывали предупреждение в любой момент времени.

Я бы хотел сделать что-нибудь вроде:

MyViewController *myVC = [[MyViewController alloc] init];
[myVC showModally];

Я хотел бы иметь возможность вызывать это из любого места в приложении и отображать его сверху. Я не хочу заботиться о текущем контроллере представления.

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

Есть мысли по этому поводу? Или, может быть, есть лучший способ добиться этого? Должен ли я просто реализовать свой собственный механизм и просто разместить представление поверх окна?


person nebs    schedule 12.04.2013    source источник
comment
вы используете раскадровки?   -  person u.gen    schedule 12.04.2013


Ответы (5)


Что ж, вы можете следить за цепочкой.

Начните с [UIApplication sharedApplication].delegate.window.rootViewController.

На каждом контроллере представления выполните следующую серию тестов.

Если [viewController isKindOfClass:[UINavigationController class]], перейдите к [(UINavigationController *)viewController topViewController].

Если [viewController isKindOfClass:[UITabBarController class]], перейдите к [(UITabBarController *)viewController selectedViewController].

Если [viewController presentedViewController], перейдите к [viewController presentedViewController].

person Jeffery Thomas    schedule 12.04.2013
comment
Я создал рекурсивный метод, используя вашу идею: gist.github.com/MartinMoizard/6537467. Работает как шарм :) - person MartinMoizard; 12.09.2013
comment
Это может не работать в достаточной степени, когда открыто одно или несколько представлений предупреждений - по крайней мере, для предупреждений старого стиля, я обнаружил, что [UIApplication sharedApplication].keyWindow является более подходящей точкой входа для цепочки. - person DrMickeyLauer; 24.04.2017
comment
@DrMickeyLauer да, это старый ответ. Я бы кое-что изменил. Теперь я бы посоветовал повторять var windows в общем приложении и повторять var childViewControllers в каждом контроллере представления. - person Jeffery Thomas; 25.04.2017

Мое решение в Swift (вдохновленное сутью MartinMoizard)

extension UIViewController {
    func presentViewControllerFromVisibleViewController(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) {
        if let navigationController = self as? UINavigationController {
            navigationController.topViewController?.presentViewControllerFromVisibleViewController(viewControllerToPresent, animated: flag, completion: completion)
        } else if let tabBarController = self as? UITabBarController {
            tabBarController.selectedViewController?.presentViewControllerFromVisibleViewController(viewControllerToPresent, animated: flag, completion: completion)
        } else if let presentedViewController = presentedViewController {
            presentedViewController.presentViewControllerFromVisibleViewController(viewControllerToPresent, animated: flag, completion: completion)
        } else {
            present(viewControllerToPresent, animated: flag, completion: completion)
        }
    }
}
person allaire    schedule 10.03.2015

Это решение дает вам самый верхний контроллер просмотра, так что вы можете обрабатывать любые особые условия перед представлением из него. Например, возможно, вы хотите представить свой контроллер представления только в том случае, если самый верхний контроллер представления не является конкретным контроллером представления.

extension UIApplication {
    /// The top most view controller
    static var topMostViewController: UIViewController? {
        return UIApplication.shared.keyWindow?.rootViewController?.visibleViewController
    }
}

extension UIViewController {
    /// The visible view controller from a given view controller
    var visibleViewController: UIViewController? {
        if let navigationController = self as? UINavigationController {
            return navigationController.topViewController?.visibleViewController
        } else if let tabBarController = self as? UITabBarController {
            return tabBarController.selectedViewController?.visibleViewController
        } else if let presentedViewController = presentedViewController {
            return presentedViewController.visibleViewController
        } else {
            return self
        }
    }
}

С его помощью вы можете представить свой контроллер представления из любого места без необходимости знать, какой контроллер представления является самым верхним.

UIApplication.topMostViewController?.present(viewController, animated: true, completion: nil)

Или представьте свой контроллер представления, только если самый верхний контроллер представления не является конкретным контроллером представления

if let topVC = UIApplication.topMostViewController, !(topVC is FullScreenAlertVC) {
    topVC.present(viewController, animated: true, completion: nil)
}

Следует отметить, что если в данный момент отображается UIAlertController, UIApplication.topMostViewController вернет UIAlertController. Представление поверх UIAlertController ведет себя странно, и этого следует избегать. Таким образом, вы должны либо вручную проверить это !(UIApplication.topMostViewController is UIAlertController) перед представлением, либо добавить else if регистр, чтобы вернуть ноль, если self is UIAlertController

extension UIViewController {
    /// The visible view controller from a given view controller
    var visibleViewController: UIViewController? {
        if let navigationController = self as? UINavigationController {
            return navigationController.topViewController?.visibleViewController
        } else if let tabBarController = self as? UITabBarController {
            return tabBarController.selectedViewController?.visibleViewController
        } else if let presentedViewController = presentedViewController {
            return presentedViewController.visibleViewController
        } else if self is UIAlertController {
            return nil
        } else {
            return self
        }
    }
}
person NSExceptional    schedule 03.08.2017
comment
Очень красивое решение! Помогло мне покопаться. Спасибо! - person Paul Lehn; 25.03.2020

Вы можете реализовать этот код в делегате вашего приложения:

AppDelegate.m

-(void)presentViewControllerFromVisibleController:(UIViewController *)toPresent
{
    UIViewController *vc = self.window.rootViewController;
    [vc presentViewController:toPresent animated:YES];
}

AppDelegate.h

-(void)presentViewControllerFromVisibleViewController:(UIViewController *)toPresent;

Откуда угодно

#import "AppDelegate.h"
...
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
[delegate presentViewControllerFromVisibleViewController:myViewControllerToPresent];

В вашем делегате вы получаете rootViewController из window. Это всегда будет видно - это «родительский» контроллер всего.

person Undo    schedule 12.04.2013
comment
Я думаю, что это обычно срабатывает, но неправильно, что корневой контроллер представления всегда виден. На экране может быть модальный контроллер представления, и я не уверен, что произойдет, если вы запустите этот код в таких обстоятельствах. - person rdelmar; 12.04.2013
comment
@rdelmar В этом случае ваш корневой VC по-прежнему будет корневым, представляющим модальный VC. - person Undo; 12.04.2013
comment
Да, это все еще корень, но он не будет работать - если на экране отображается модальный контроллер представления, и вы пытаетесь представить другой из корневого виртуального сервера, вы получаете предупреждение, а контроллер не отображается (попытка присутствует ... на ‹ViewController:›, представление которого не находится в иерархии окон!). - person rdelmar; 12.04.2013
comment
@rdelmar Да, думаю, это правда. Мораль истории: не вызывайте модальные VC из модальных VC, используя этот метод. Используйте себя в качестве ... Подождите - только не вызывайте модальные VC дважды! - person Undo; 12.04.2013
comment
Совершенно нормально вызывать модальные окна дважды, вам просто нужно вызвать вторую из первой. - person rdelmar; 12.04.2013
comment
Я закончил тем, что представил VC в rootViewController окна, но получил доступ к окну непосредственно из UIApplication view keyWindow вместо того, чтобы проходить через делегата приложения. Есть ли конкретная причина, по которой вы прошли через делегата приложения здесь? - person nebs; 14.04.2013
comment
@Nebs Потому что именно так я и умею :) Не знал про keyWindow. - person Undo; 14.04.2013

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

Файл интерфейса: MyModalViewController.h

#import <UIKit/UIKit.h>

@interface MyModalViewController : UIViewController
- (void) show;
@end

Файл реализации: MyModalViewController.m

#import "MyModalViewController.h"


@implementation MyModalViewController

- (void) show {
    UIWindow *window = [[UIApplication sharedApplication] keyWindow];
    //  Configure the frame of your modal's view.
    [window addSubview: self.view];
}

@end
person Jason Barker    schedule 12.04.2013
comment
вызывается ли viewDidLoad? поскольку VC не входит в иерархию VC? - person Alex Bollbach; 30.11.2018