iOS Presente controller de vista modal al inicio sin flash

Me gustaría presentar modalmente, al inicio, un asistente tutorial para el usuario.

¿Hay alguna manera de presentar un UIViewController modal en el inicio de la aplicación, sin ver, al less durante un milisegundo, el rootViewController detrás de él?

Ahora estoy haciendo algo como esto (omitiendo los controles de primer lanzamiento para mayor claridad):

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // ... UIStoryboard *storyboard = self.window.rootViewController.storyboard; TutorialViewController* tutorialViewController = [storyboard instantiateViewControllerWithIdentifier:@"tutorial"]; tutorialViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; [self.window makeKeyAndVisible]; [self.window.rootViewController presentViewController:tutorialViewController animated:NO completion:NULL]; } 

sin suerte He intentado mover [self.window makeKeyAndVisible]; para antes de la [... presentViewController:tutorialViewController ...] , pero el modal no aparece.

Todos los methods presentViewController requieren que el controller de vista presentador haya aparecido primero. Para ocultar el VC de la raíz se debe presentar una superposition. La pantalla de inicio se puede seguir presentando en la window hasta que la presentación se haya completado y luego se desvanezca la superposition.

  UIView* overlayView = [[[UINib nibWithNibName:@"LaunchScreen" bundle:nil] instantiateWithOwner:nil options:nil] firstObject]; overlayView.frame = self.window.rootViewController.view.bounds; overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; UIStoryboard *storyboard = self.window.rootViewController.storyboard; TutorialViewController* tutorialViewController = [storyboard instantiateViewControllerWithIdentifier:@"tutorial"]; tutorialViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; [self.window makeKeyAndVisible]; [self.window addSubview:overlayView]; [self.window.rootViewController presentViewController:tutorialViewController animated:NO completion:^{ NSLog(@"displaying"); [UIView animateWithDuration:0.5 animations:^{ overlayView.alpha = 0; } completion:^(BOOL finished) { [overlayView removeFromSuperview]; }]; }]; 

puede ser que tu puedas usar el "childViewController"

 UIStoryboard *storyboard = self.window.rootViewController.storyboard; TutorialViewController* tutorialViewController = [storyboard instantiateViewControllerWithIdentifier:@"tutorial"]; [self.window addSubview: tutorialViewController.view]; [self.window.rootViewController addChildViewController: tutorialViewController]; [self.window makeKeyAndVisible]; 

Cuando necesite rechazar a su tutor, puede eliminar su vista de la supervisión. También puede agregar alguna animation en la vista al establecer la propiedad alfa. Espero que sea útil 🙂

La respuesta de Bruce en Swift 3:

 if let vc = window?.rootViewController?.storyboard?.instantiateViewController(withIdentifier: "LOGIN") { let launch = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()! launch.view.frame = vc.view.bounds launch.view.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight] window?.makeKeyAndVisible() window?.addSubview(launch.view) //Using DispatchQueue to prevent "Unbalanced calls to begin/end appearance transitions" DispatchQueue.global().async { // Bounce back to the main thread to update the UI DispatchQueue.main.async { self.window?.rootViewController?.present(vc, animated: false, completion: { UIView.animate(withDuration: 0.5, animations: { launch.view.alpha = 0 }, completion: { (_) in launch.view.removeFromSuperview() }) }) } } } 

Este problema aún existe en iOS 10. Mi solución fue:

  1. en viewWillAppear agregar el VC modal como un childVC a la raízVC
  2. en la viewDidAppear :
    1. Elimine modalVC como un elemento secundario de rootVC
    2. Modalmente presente el childVC sin animation

Código:

 extension UIViewController { func embed(childViewController: UIViewController) { childViewController.willMove(toParentViewController: self) view.addSubview(childViewController.view) childViewController.view.frame = view.bounds childViewController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth] addChildViewController(childViewController) } func unembed(childViewController: UIViewController) { assert(childViewController.parent == self) childViewController.willMove(toParentViewController: nil) childViewController.view.removeFromSuperview() childViewController.removeFromParentViewController() } } class ViewController: UIViewController { let modalViewController = UIViewController() override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) //BUG FIX: We have to embed the VC rather than modally presenting it because: // - Modal presentation within viewWillAppear(animated: false) is not allowed // - Modal presentation within viewDidAppear(animated: false) is not visually glitchy //The VC is presented modally in viewDidAppear: if self.shouldPresentModalVC { embed(childViewController: modalViewController) } //... } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) //BUG FIX: Move the embedded VC to be a modal VC as is expected. See viewWillAppear if modalViewController.parent == self { unembed(childViewController: modalViewController) present(modalViewController, animated: false, completion: nil) } //.... } } 

La respuesta de Bruce me indicó en la dirección correcta, pero como mi modal puede aparecer más a menudo que solo en el inicio (es una pantalla de inicio de session, por lo que debe aparecer si se desconectan), no quería vincular mi superposition directamente al presentación del controller de vista.

Aquí está la lógica que se me ocurrió:

  self.window.rootViewController = _tabBarController; [self.window makeKeyAndVisible]; WSILaunchImageView *launchImage = [WSILaunchImageView new]; [self.window addSubview:launchImage]; [UIView animateWithDuration:0.1f delay:0.5f options:0 animations:^{ launchImage.alpha = 0.0f; } completion:^(BOOL finished) { [launchImage removeFromSuperview]; }]; 

En una sección diferente, realizo la lógica de presentar mi VC de inicio de session en el típico self.window.rootViewController presentViewController:... formatting que puedo usar independientemente de si se trata de un inicio de aplicación o de lo contrario.

Si a alguien le importa, aquí es cómo creé mi vista de superposition:

 @implementation WSILaunchImageView - (instancetype)init { self = [super initWithFrame:[UIScreen mainScreen].bounds]; if (self) { self.image = WSILaunchImage(); } return self; } 

Y aquí está la lógica de la image de lanzamiento en sí:

 UIImage * WSILaunchImage() { static UIImage *launchImage = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if (WSIEnvironmentDeviceHas480hScreen()) launchImage = [UIImage imageNamed:@"LaunchImage-700"]; else if (WSIEnvironmentDeviceHas568hScreen()) launchImage = [UIImage imageNamed:@"LaunchImage-700-568h"]; else if (WSIEnvironmentDeviceHas667hScreen()) launchImage = [UIImage imageNamed:@"LaunchImage-800-667h"]; else if (WSIEnvironmentDeviceHas736hScreen()) launchImage = [UIImage imageNamed:@"LaunchImage-800-Portrait-736h"]; }); return launchImage; } 

Aaaa y solo para completar, aquí es cómo se ven los methods de EnvironmentDevice:

 static CGSize const kIPhone4Size = (CGSize){.width = 320.0f, .height = 480.0f}; BOOL WSIEnvironmentDeviceHas480hScreen(void) { static BOOL result = NO; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ result = CGSizeEqualToSize([UIScreen mainScreen].bounds.size, kIPhone4Size); }); return result; } 

Puede ser una mala solución, pero podría hacer un ViewController con 2 contenedores, donde ambos contenedores están vinculados a un VC cada uno. Entonces puedes controlar qué contenedor debe estar visible en el código, esa es una idea

 if (!firstRun) { // Show normal page normalContainer.hidden = NO; firstRunContainer.hidden = YES; } else if (firstRun) { // Show first run page or something similar normalContainer.hidden = YES; firstRunContainer.hidden = NO; } 
 let vc = UIViewController() vc.modalPresentationStyle = .custom vc.transitioningDelegate = noFlashTransitionDelegate present(vc, animated: false, completion: nil) class NoFlashTransitionDelegate: NSObject, UIViewControllerTransitioningDelegate { public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { if source.view.window == nil, let overlayViewController = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController(), let overlay = overlayViewController.view { source.view.addSubview(overlay) UIView.animate(withDuration: 0, animations: {}) { (finished) in overlay.removeFromSuperview() } } return nil } } 

Así es como lo hago con storyboards y funciona con múltiples modalidades. Este ejemplo tiene 3. Abajo, medio y superior.

Solo asegúrese de que el storyboardID de cada viewController esté configurado correctamente en el generador de interfaces.

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { UIStoryboard * storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; BottomViewController *bottomViewController = [storyboard instantiateViewControllerWithIdentifier:@"BottomViewController"]; UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; [window setRootViewController:bottomViewController]; [window makeKeyAndVisible]; if (!_loggedIn) { MiddleViewController *middleViewController = [storyboard instantiateViewControllerWithIdentifier:@"middleViewController"]; TopViewController *topViewController = [storyboard instantiateViewControllerWithIdentifier:@"topViewController"]; [bottomViewController presentViewController:middleViewController animated:NO completion:nil]; [middleViewController presentViewController:topViewController animated:NO completion:nil]; } else { // setup as you normally would. } self.window = window; return YES; }