Le contrôleur de vue racine d’une fenêtre iOS est-il généralement initialisé une fois au début sur un contrôleur de barre d’onglet ou de navigation? Est-il possible de changer le contrôleur de vue racine plusieurs fois dans une application?
J'ai un scénario où la vue de dessus est différente en fonction de l'action de l'utilisateur. Je pensais avoir un contrôleur de navigation avec le contrôleur de la vue de dessus ayant l'image de l'écran de démarrage et pousser/faire apparaître des contrôleurs de vue selon les besoins. Alternativement, je peux continuer à changer le contrôleur de vue supérieure de la fenêtre. Quelle sera la meilleure approche?
Il est plus habituel d’utiliser un "contrôleur de vue présentée" (presentViewController:animated:completion:
). Vous pouvez en avoir autant que vous le souhaitez, en apparaissant effectivement devant (et en remplaçant fondamentalement) le contrôleur de vue racine. Il ne doit y avoir aucune animation si vous ne voulez pas, ou il peut y avoir. Vous pouvez ignorer le contrôleur de vue présenté pour revenir au contrôleur de vue racine d'origine, mais ce n'est pas obligatoire; le contrôleur de vue présenté peut simplement être là pour toujours si vous le souhaitez.
Voici la section sur les contrôleurs de vue présentés dans mon livre:
http://www.apeth.com/iOSBook/ch19.html#_presented_view_controller
Dans ce diagramme (de plus tôt dans ce chapitre), un contrôleur de vue présenté a entièrement pris en charge l'interface de l'application; le contrôleur de vue racine et ses sous-vues ne sont plus dans l'interface. Le contrôleur de vue racine existe toujours, mais il est léger et sans importance.
iOS 8.0, Xcode 6.0.1, ARC activé
La plupart de vos questions ont reçu une réponse. Cependant, je peux m'attaquer à un problème que j'ai récemment dû affronter moi-même.
Est-il possible de changer le contrôleur de vue racine plusieurs fois, au sein d'une application?
La réponse est oui. Je devais le faire récemment pour réinitialiser ma hiérarchie UIView après les premières UIViews faisant partie de l'application. la mise en route n'était plus nécessaire. En d'autres termes, vous pouvez réinitialiser votre "rootViewController" depuis n'importe quel autre UIViewController à tout moment après l'application. "didFinishLoadingWithOptions".
Pour faire ça ...
1) Déclarez une référence à votre application. délégué (app appelé "Test") ...
TestAppDelegate *testAppDelegate = (TestAppDelegate *)[UIApplication sharedApplication].delegate;
2) Choisissez un UIViewController que vous souhaitez créer avec "rootViewController"; soit du storyboard ou définir par programme ...
UIStoryboard *mainStoryBoard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
NewRootViewController *newRootViewController = [mainStoryBoard instantiateViewControllerWithIdentifier:@"NewRootViewController"];
UIViewController *newRootViewController = [[UIViewController alloc] init];
newRootViewController.view = [[UIView alloc] initWithFrame:CGRectMake(0, 50, 320, 430)];
newRootViewController.view.backgroundColor = [UIColor whiteColor];
3) Mettre tout cela ensemble ...
testAppDelegate.window.rootViewController = newRootViewController;
[testAppDelegate.window makeKeyAndVisible];
4) Vous pouvez même lancer une animation ...
testAppDelegate.window.rootViewController = newRootViewController;
[testAppDelegate.window makeKeyAndVisible];
newRootViewController.view.alpha = 0.0;
[UIView animateWithDuration:2.0 animations:^{
newRootViewController.view.alpha = 1.0;
}];
J'espère que cela aide quelqu'un! À votre santé.
Le contrôleur de vue racine de la fenêtre.
Le contrôleur de vue racine fournit la vue du contenu de la fenêtre. L'affectation d'un contrôleur de vue à cette propriété (par programme ou à l'aide d'Interface Builder) installe la vue du contrôleur de vue en tant que vue de contenu de la fenêtre. Si la fenêtre possède une hiérarchie de vues existante, les anciennes vues sont supprimées avant les nouvelles. La valeur par défaut de cette propriété est nil.
* Mise à jour 9/2/2015
Comme le soulignent les commentaires ci-dessous, vous devez gérer la suppression de l'ancien contrôleur de vue lorsque le nouveau contrôleur de vue est présenté. Vous pouvez choisir d'avoir un contrôleur de vue transitoire dans lequel vous allez gérer cela. Voici quelques astuces sur la façon de mettre en œuvre ceci:
[UIView transitionWithView:self.containerView
duration:0.50
options:options
animations:^{
//Transition of the two views
[self.viewController.view removeFromSuperview];
[self.containerView addSubview:aViewController.view];
}
completion:^(BOOL finished){
//At completion set the new view controller.
self.viewController = aViewController;
}];
À partir des commentaires sur la réponse de serge-k, j'ai élaboré une solution efficace avec un contournement du comportement étrange lorsqu'un contrôleur de vue modale est présenté sur l'ancien rootViewController:
extension UIView {
func snapshot() -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.mainScreen().scale)
drawViewHierarchyInRect(bounds, afterScreenUpdates: true)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result
}
}
extension UIWindow {
func replaceRootViewControllerWith(_ replacementController: UIViewController, animated: Bool, completion: (() -> Void)?) {
let snapshotImageView = UIImageView(image: self.snapshot())
self.addSubview(snapshotImageView)
let dismissCompletion = { () -> Void in // dismiss all modal view controllers
self.rootViewController = replacementController
self.bringSubview(toFront: snapshotImageView)
if animated {
UIView.animate(withDuration: 0.4, animations: { () -> Void in
snapshotImageView.alpha = 0
}, completion: { (success) -> Void in
snapshotImageView.removeFromSuperview()
completion?()
})
}
else {
snapshotImageView.removeFromSuperview()
completion?()
}
}
if self.rootViewController!.presentedViewController != nil {
self.rootViewController!.dismiss(animated: false, completion: dismissCompletion)
}
else {
dismissCompletion()
}
}
}
Pour remplacer le rootViewController, utilisez simplement:
let newRootViewController = self.storyboard!.instantiateViewControllerWithIdentifier("BlackViewController")
UIApplication.sharedApplication().keyWindow!.replaceRootViewControllerWith(newRootViewController, animated: true, completion: nil)
J'espère que cela vous aidera :) testé sur iOS 8.4; également testé pour la prise en charge des contrôleurs de navigation (devrait également prendre en charge les contrôleurs de tabulation, etc., mais je ne l'ai pas testé)
Explication
Si un contrôleur de vue modale est présenté sur l'ancien rootViewController, celui-ci est remplacé, mais l'ancienne vue reste toujours suspendue au-dessous de la nouvelle vue de rootViewController (et peut être vue par exemple lors d'animations de transition Flip horizontal ou Fondu enchaîné) et de l'ancien contrôleur de vue. la hiérarchie reste allouée (ce qui peut entraîner de graves problèmes de mémoire si le remplacement se produit plusieurs fois).
La seule solution consiste donc à ignorer tous les contrôleurs de vue modale, puis à remplacer le rootViewController. Un instantané de l’écran est placé sur la fenêtre pendant le retrait et le remplacement afin de masquer le processus de clignotement déplaisant.
Vous pouvez modifier rootViewController de la fenêtre tout au long du cycle de vie de l'application.
UIViewController *viewController = [UIViewController alloc] init];
[self.window setRootViewController:viewController];
Lorsque vous modifiez rootViewController, vous pouvez toujours souhaiter ajouter un UIImageView en tant que sous-vue de la fenêtre pour qu’elle se comporte comme une image de démarrage. J'espère que cela a du sens, quelque chose comme ça:
- (void) addSplash {
CGRect rect = [UIScreen mainScreen].bounds;
UIImageView *splashImage = [[UIImageView alloc] initWithFrame:rect];
splashImage.image = [UIImage imageNamed:@"splash.png"];
[self.window addSubview:splashImage];
}
- (void) removeSplash {
for (UIView *view in self.window.subviews) {
if ([view isKindOfClass:[UIImageView class]]) {
[view removeFromSuperview];
}
}
}
Pour iOS8, nous devons également définir ci-dessous deux paramètres sur YES.
providesPresentationContextTransitionStyle
definesPresentationContext
Voici mon code pour présenter le contrôleur de vue de modèle transparent sous le contrôleur de navigation pour iOS 6 et versions ultérieures.
ViewController *vcObj = [[ViewController alloc] initWithNibName:NSStringFromClass([ViewController class]) bundle:nil];
UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:vcObj];
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
navCon.providesPresentationContextTransitionStyle = YES;
navCon.definesPresentationContext = YES;
navCon.modalPresentationStyle = UIModalPresentationOverCurrentContext;
[self presentViewController:navCon animated:NO completion:nil];
}
else {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[self presentViewController:navCon animated:NO completion:^{
[navCon dismissViewControllerAnimated:NO completion:^{
appDelegate.window.rootViewController.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentViewController:navCon animated:NO completion:nil];
appDelegate.window.rootViewController.modalPresentationStyle = UIModalPresentationFullScreen;
}];
}];
}