Текущий контроллер представления при запуске приложения

EDIT. В этом потоке нет принятого ответа, но здесь есть творческое решение:

iOS Представлен контроллер модального просмотра при запуске без флэш-памяти


В моем приложении есть экран настройки, который должен быть представлен модально на контроллере корневого представления, если выполняются определенные условия.

Я посмотрел на SO и в интернете, и ближайший ответ до сих пор о том, как это сделать:

AppDelegate, rootViewController и presentViewController

Есть 2 проблемы с этим подходом:

  • В iOS 8 это делается так, что в консоли появляется журнал, который, похоже, не является ошибкой, но, вероятно, не очень хорош:

Unbalanced calls to begin/end appearance transitions for UITabBarController: 0x7fe20058d570.

  1. Контроллер корневого представления на самом деле очень быстро появляется, когда приложение запускается, а затем исчезает в представленном контроллере представления (хотя я явно вызываю animated:NO по моему методу presentViewController).

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

Представление контроллера представления в моем контроллере корневого представления viewDidLoad также приводит к заметному моменту появления пользовательского интерфейса при первом запуске приложения.

Можно ли программно представить контроллер представления по модулю до того, как приложение отобразило что-либо, чтобы первый вид на месте был модульным контроллером представления?

ОБНОВЛЕНИЕ: Благодарим вас за комментарии, добавив мой текущий код:

В моем AppDelegate.m:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];

    [self.window makeKeyAndVisible];
    [self.window.rootViewController presentViewController:[storyboard instantiateViewControllerWithIdentifier:@"setupViewController"] animated:NO completion:NULL];

    return YES;
}

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

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

- (void)viewDidLoad
{
    [self.view addSubview:setupViewController.view];
    [self addChildViewController:setupViewController];
}

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

ОБНОВЛЕНИЕ 2:. Пробуя много вопросов и ожидая ответа на 2 месяца, этот вопрос предлагает наиболее креативное решение:

iOS Представлен контроллер модального просмотра при запуске без флэш-памяти

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

Спасибо @Stakenborg за то, что указали мне на эту тему.

Ответы

Ответ 1

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

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

A. добавьте startScreen в качестве подзаголовка, затем нажмите, затем снимите экран запуска

Решение представлено здесь ullstrm и не страдает от несбалансированных вызовов для перехода на начало/конец:

let launchScreenView = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()!.view!
launchScreenView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
launchScreenView.frame = window!.rootViewController!.view.bounds
window?.rootViewController?.view.addSubview(launchScreenView)
window?.makeKeyAndVisible()
// avoiding: Unbalanced calls to begin/end appearance transitions.
DispatchQueue.global().async {
    DispatchQueue.main.async {
        self.window?.rootViewController?.present(myViewControllerToPresent, animated: false, completion: {
            launchScreenView.removeFromSuperview()
        })
    }
}

В. addChildViewController сначала, а затем удалить, а затем представить

Решение представлено здесь Бенедиктом Коэном.

Ответ 2

Я был в той же лодке, что и вы, и нашел тот же ответ. Я узнал, что вы можете избавиться от первой проблемы (предупреждение о несимметричных вызовах), установив modalPresentationStyle вашего setupViewController на .OverCurrentContext или .OverFullScreen. Проблема решена - так я и думал.

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

Как вам, мне нужно решение с нормальной иерархией взглядов, и я не хотел "подделывать" что-то. Я думаю, что самым изящным решением является переключение окон rootViewController при первом увольнении вашего setupViewController.

Итак, при запуске вы устанавливаете setupViewController как rootViewController (если необходимо):

var window: UIWindow?
var tabBarController: UITabBarController!

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    tabBarController = window!.rootViewController as! UITabBarController
    if needsToShowSetup() {
        let setupViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("SetupViewController") as! SetupViewController
        window?.rootViewController = setupViewController
    }
    return true
}

Когда настройка выполнена, вы вызываете метод в вашем приложении appDelegate для переключения на "настоящий" rootViewController:

func switchToTabBarController() {
    let setupUpViewController = window!.rootViewController!

    tabBarController.view.frame = window!.bounds
    window!.insertSubview(tabBarController.view, atIndex: 0)

    let height = setupUpViewController.view.bounds.size.height
    UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1, options: .allZeros, animations: { () -> Void in
        setupUpViewController.view.transform = CGAffineTransformMakeTranslation(0, height)
        }) { (completed) -> Void in
            self.window!.rootViewController = self.tabBarController
    }
}

Я был после анимации с закрытием по вертикали. Для кроссфейда и других вы можете использовать UIView.transitionFromView(fromView: UIView, toView: UIView...). В дальнейшем вы можете представить/отклонить свой setupController обычным способом, поэтому ваше действие doneButton может быть примерно таким:

@IBAction func doneButtonSelected(sender: UIButton) {
    if presentingViewController != nil {
        presentingViewController!.dismissViewControllerAnimated(true, completion: nil)
    } else {
        let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
        appDelegate.switchToTabBarController()
    }
}

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

Ответ 3

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

У меня есть два предложения. Один из них - попытаться сделать это в viewDidAppear:. Я попробовал это, и хотя вы видите представление контроллера корневого представления, если вы внимательно смотрите, вы его почти не видите, и иногда вы его вообще не видите, если вы моргаете:

-(void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self presentViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"setupViewController"] animated:NO completion:NULL];
}

Конечно, вам нужно добавить флаг, чтобы вы не делали этого каждый раз при вызове viewDidAppear: - иначе вы никогда не сможете вернуться к этому контроллеру просмотра вообще! Но это тривиально, и я оставляю это как упражнение для читателя.

Мое другое предложение - и вы четко подумали об этом - это использовать вместо этого встроенный (дочерний) контроллер просмотра. Это работает вокруг ограничений всей "презентации".

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

И затем, когда процедура настройки пользователя закончена, и пользователь "отклоняет" это представление, вы разрываете это представление вниз с помощью анимации и удаляете контроллер дочернего представления - раскрывая представление контроллера корневого представления под ним. Анимация сделает все это неотличимым от увольнения представленного представления, хотя это и не совсем одно.