Contrôleur de vue modale présent au démarrage sans flash

Je voudrais présenter de manière modale, au premier démarrage, un assistant de tutoriel à l'user.

Est-il possible de présenter un UIViewController modal au démarrage de l'application, sans voir, au less pour une milliseconde, le rootViewController derrière lui?

Maintenant, je fais quelque chose comme ça (en omettant les premières vérifications de lancement pour plus de clarté):

 - (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]; } 

sans chance. J'ai essayé de déplacer [self.window makeKeyAndVisible]; à avant l' [... presentViewController:tutorialViewController ...] , mais le modal n'apparaît même pas.

Toutes les methods presentViewController nécessitent que le controller de présentation soit apparu en premier. Afin de cacher le VC racine, une superposition doit être présentée. L'écran de lancement peut continuer à être affiché sur la window jusqu'à la fin de la présentation, puis disparaître de 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]; }]; }]; 

peut être votre pouvez utiliser le "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]; 

Lorsque vous devez renvoyer votre tuteur, vous pouvez supprimer son affichage de la vue d'set. En outre, vous pouvez append une animation sur la vue en définissant la propriété alpha.Hope utile 🙂

La réponse aisée de Bruce dans 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() }) }) } } } 

Ce problème existe toujours dans iOS 10. Mon correctif était:

  1. dans viewWillAppear append le VC modal en tant que childVC au rootVC
  2. dans viewDidAppear :
    1. Supprimer le modalVC en tant qu'enfant de la racineVC
    2. Présenter modalement l'enfantVC sans animation

Code:

 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 réponse de Bruce m'a orienté dans la bonne direction, mais parce que mon modal peut apparaître plus souvent qu'au lancement (c'est un écran de connection, il doit donc apparaître en cas de déconnection), je ne voulais pas lier directement ma superposition au présentation du controller de vue.

Voici la logique que je suis venu avec:

  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]; }]; 

Dans une section différente, self.window.rootViewController presentViewController:... la logique de présentation de mon identifiant de connection dans le self.window.rootViewController presentViewController:... format que je peux utiliser, qu'il s'agisse d'un lancement d'application ou autre.

Si quelqu'un s'en soucie, voici comment j'ai créé ma vue de superposition:

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

Et voici la logique de l'image de lancement elle-même:

 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; } 

Aaaaand juste pour l'amour de l'achèvement, voici à quoi ressemblent ces methods 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; } 

Peut être une mauvaise solution, mais vous pourriez faire un ViewController avec 2 conteneurs, où les deux conteneurs sont liés à un VC chacun. Ensuite, vous pouvez contrôler quel conteneur doit être visible dans le code, c'est une idée

 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 } } 

C'est comme ça que je le fais avec les storyboards et ça marche avec plusieurs modaux. Cet exemple a 3. Bottom, middle et top.

Assurez-vous simplement que le storyboardID de chaque viewController est correctement défini dans le générateur d'interface.

 - (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; }