web-dev-qa-db-fra.com

Échange de vues enfant dans une vue conteneur

Soit ContainerView la vue du conteneur parent avec deux vues de contenu enfant: NavigationView et ContentView.

Example of View Layout

J'aimerais pouvoir échanger le contrôleur de ContentView avec une autre vue. Par exemple, échanger un contrôleur de page d'accueil avec un contrôleur de page d'actualités. Actuellement, la seule façon de penser à cela est d'utiliser un délégué pour dire au ContainerView que je veux changer de vue. Cela semble être une façon bâclée de le faire parce que le ContainerViewController finirait par avoir un tas de délégués spéciaux pour toutes les sous-vues.

Cela doit également communiquer avec le NavigationView qui a des informations sur la vue qui se trouve actuellement dans le ContentView. Par exemple: si l'utilisateur est sur la page d'actualités, la barre de navigation dans la vue de navigation indiquera que le bouton d'actualités est actuellement sélectionné.

Question A: Existe-t-il un moyen de permuter le contrôleur dans ContentView sans méthode déléguée appelant ContainerView lui-même? Je voudrais le faire par programme (pas de storyboard).

Question B: Comment puis-je permuter les contrôleurs dans ContentView de NavigationView sans appel délégué? Je voudrais le faire par programme (pas de storyboard).

23
Alex

Lorsque vous avez des vues enfants qui ont leurs propres contrôleurs de vue, vous devez suivre le modèle de contrôleur de conteneur personnalisé. Voir Création de contrôleurs de vue de conteneur personnalisés pour plus d'informations.

En supposant que vous ayez suivi le modèle de conteneur personnalisé, lorsque vous souhaitez modifier le contrôleur de vue enfant (et sa vue associée) pour la "vue de contenu", vous le feriez par programme avec quelque chose comme:

UIViewController *newController = ... // instantiate new controller however you want
UIViewController *oldController = ... // grab the existing controller for the current "content view"; perhaps you maintain this in your own ivar; perhaps you just look this up in self.childViewControllers

newController.view.frame = oldController.view.frame;

[oldController willMoveToParentViewController:nil];
[self addChildViewController:newController];         // incidentally, this does the `willMoveToParentViewController` for the new controller for you

[self transitionFromViewController:oldController
                  toViewController:newController
                          duration:0.5
                           options:UIViewAnimationOptionTransitionCrossDissolve
                        animations:^{
                            // no further animations required
                        }
                        completion:^(BOOL finished) {
                            [oldController removeFromParentViewController]; // incidentally, this does the `didMoveToParentViewController` for the old controller for you
                            [newController didMoveToParentViewController:self];
                        }];

Lorsque vous le faites de cette façon, il n'y a pas besoin d'interface de protocole délégué avec le contrôleur de la vue de contenu (autre que ce que iOS fournit déjà avec les méthodes Gestion des contrôleurs de vue enfant dans un conteneur personnalisé ).


Soit dit en passant, cela suppose que le contrôleur enfant initial associé à cette vue de contenu a été ajouté comme suit:

UIViewController *childController = ... // instantiate the content view's controller any way you want
[self addChildViewController:childController];
childController.view.frame = ... // set the frame any way you want
[self.view addSubview:childController.view];
[childController didMoveToParentViewController:self];

Si vous souhaitez qu'un contrôleur enfant indique au parent de modifier le contrôleur associé à la vue de contenu, vous devez:

  1. Définissez un protocole pour cela:

    @protocol ContainerParent <NSObject>
    
    - (void)changeContentTo:(UIViewController *)controller;
    
    @end
    
  2. Définissez le contrôleur parent pour se conformer à ce protocole, par exemple:

    #import <UIKit/UIKit.h>
    #import "ContainerParent.h"
    
    @interface ViewController : UIViewController <ContainerParent>
    
    @end
    
  3. Implémentez la méthode changeContentTo dans le contrôleur parent (comme indiqué ci-dessus):

    - (void)changeContentTo:(UIViewController *)controller
    {
        UIViewController *newController = controller;
        UIViewController *oldController = ... // grab reference of current child from `self.childViewControllers or from some property where you stored it
    
        newController.view.frame = oldController.view.frame;
    
        [oldController willMoveToParentViewController:nil];
        [self addChildViewController:newController];
    
        [self transitionFromViewController:oldController
                          toViewController:newController
                                  duration:1.0
                                   options:UIViewAnimationOptionTransitionCrossDissolve
                                animations:^{
                                    // no further animations required
                                }
                                completion:^(BOOL finished) {
                                    [oldController removeFromParentViewController];
                                    [newController didMoveToParentViewController:self];
                                }];
    }
    
  4. Et les contrôleurs enfants peuvent désormais utiliser ce protocole en référence à self.parentViewController propriété qu'iOS met à votre disposition:

    - (IBAction)didTouchUpInsideButton:(id)sender
    {
        id <ContainerParent> parentViewController = (id)self.parentViewController;
        NSAssert([parentViewController respondsToSelector:@selector(changeContentTo:)], @"Parent must conform to ContainerParent protocol");
    
        UIViewController *newChild = ... // instantiate the new child controller any way you want
        [parentViewController changeContentTo:newChild];
    }
    
38
Rob

Pour ce type de transition, vous pouvez également utiliser UIView.animateWith... animations.

Par exemple, supposons que rootContainerView est le conteneur et contentViewController le contrôleur actuellement actif dans le conteneur, puis

func setContentViewController(contentViewController:UIViewController, animated:Bool = true) {
    if animated == true {
        addChildViewController(contentViewController)
        contentViewController.view.alpha = 0
        contentViewController.view.frame = rootContainerView.bounds
        rootContainerView.addSubview(contentViewController.view)
        self.contentViewController?.willMoveToParentViewController(nil)

        UIView.animateWithDuration(0.3, animations: {
            contentViewController.view.alpha = 1
            }, completion: { (_) in
                contentViewController.didMoveToParentViewController(self)

                self.contentViewController?.view.removeFromSuperview()
                self.contentViewController?.didMoveToParentViewController(nil)
                self.contentViewController?.removeFromParentViewController()
                self.contentViewController = contentViewController
        })
    } else {
        cleanUpChildControllerIfPossible()

        contentViewController.view.frame = rootContainerView.bounds
        addChildViewController(contentViewController)
        rootContainerView.addSubview(contentViewController.view)
        contentViewController.didMoveToParentViewController(self)
        self.contentViewController = contentViewController
    }
}

// MARK: - Private

private func cleanUpChildControllerIfPossible() {
    if let childController = contentViewController {
        childController.willMoveToParentViewController(nil)
        childController.view.removeFromSuperview()
        childController.removeFromParentViewController()
    }
}

cela vous fournira des animations de fondu simples, vous pouvez également essayer n'importe quelle UIViewAnimationOptions, transitions, etc.

2
gbk