Je voudrais présenter modalement, au premier démarrage, un assistant de tutoriel à l'utilisateur.
Existe-t-il un moyen de présenter une UIViewController
modale au démarrage de l'application, sans voir, au moins pendant une milliseconde, la rootViewController
derrière?
Maintenant, je fais quelque chose comme ceci (en omettant les vérifications de premier 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'instruction [... presentViewController:tutorialViewController ...]
, mais le modal n'apparaît même pas.
Toutes les méthodes presentViewController exigent que le contrôleur de vue de présentation soit apparu en premier. Afin de masquer la racine VC, une superposition doit être présentée. L’écran de lancement peut continuer à être présenté sur la fenêtre jusqu’à ce que la présentation soit terminée, puis disparaître progressivement.
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 peut 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 sa vue de la vue d'ensemble. Vous pouvez également ajouter une animation à la vue en définissant la propriété alpha.Hope utile :)
Réponse positive 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 sous iOS 10. Mon correctif était le suivant:
viewWillAppear
ajouter le modal VC en tant que childVC à rootVCviewDidAppear
: 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 comme mon modal peut apparaître plus souvent que juste au lancement (c'est un écran de connexion, il doit donc apparaître s'il se déconnecte), je ne voulais pas lier directement ma superposition présentation du contrôleur 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, j'exécute la logique de présentation de mon identifiant de connexion VC au format self.window.rootViewController presentViewController:...
typique que je peux utiliser, qu'il s'agisse d'un lancement d'application ou autrement.
Si cela vous intéresse, voici comment j'ai créé ma vue superposée:
@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;
}
En guise de conclusion, voici à quoi ressemblent ces méthodes 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;
}
Il peut être tard mais dans votre AppDelegate, vous pouvez faire ceci:
//Set your rootViewController
self.window.rootViewController=myRootViewController;
//Hide the rootViewController to avoid the flash
self.window.rootViewController.view.hidden=YES;
//Display the window
[self.window makeKeyAndVisible];
if(shouldPresentModal){
//Present your modal controller
UIViewController *lc_viewController = [UIViewController new];
UINavigationController *lc_navigationController = [[UINavigationController alloc] initWithRootViewController:lc_viewController];
[self.window.rootViewController presentViewController:lc_navigationController animated:NO completion:^{
//Display the rootViewController to show your modal
self.window.rootViewController.view.hidden=NO;
}];
}
else{
//Otherwise display the rootViewController
self.window.rootViewController.view.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
}
}
Peut-être une mauvaise solution, mais vous pouvez créer un ViewController avec 2 conteneurs, les deux conteneurs étant 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;
}