Objectif c – Basculer entre les vues avec un ViewController ou plus?

Je construis une application de localization qui montre à l'user quelques endroits autour de lui.
J'ai un NearbyPlacesViewController avec un contrôle de segment qui a deux buttons "list" et "map"

Si l'user appuie sur "list" – je lui montre une vue de table avec la list des lieux autour de lui
Si l'user appuie sur "carte" – La vue se returnne et je montre à l'user une carte avec les endroits sur elle comme des épingles.

Dans "list" j'utilise aussi un UISearchBar et un UISearchDisplayController pour chercher dans la table
Dans "map" j'ai aussi d'autres sous-vues à côté de la mapView

Actuellement je garde toutes les vues ( UITableView, MKMapView, UISearchBar et plus …)
et les methods de délégué ( UITableViewDelegate, UITableViewDataSource, MKMapViewDelegate, UISearchDisplayDelegate et plus ..)
dans le NearbyPlacesViewController .

Lorsque l'user appuie sur le button "map" je cache toutes les vues relatives à la vue "list" (tableView, barre de search …), et je décoche toutes les vues relatives à la vue "map" (mapView, quelques autres sous-vues …), j'utilise ensuite UIView transitionWithView pour faire basculer l'animation entre eux.

Tout fonctionne mais il semble un peu brouillon, et le résultat est un grand NearbyPlacesViewController avec beaucoup de methods de code et de délégué.

Est-il préférable de le faire avec des viewControllers séparés?
Et si oui, comment je fais? est-ce que je crée ListViewController et MapViewController et les place dans un NearbyViewController?
Comment partager le model entre eux?

Juste pour que vous ayez une idée de ce à quoi pourrait ressembler le confinement du controller de vue (iOS 5), c'est une façon de le faire. Il se compose de quatre classs, le controller de vue de conteneur (dont la vue aura le contrôle segmenté pour basculer entre deux controllers de vue enfant), deux controllers de vue enfant randoms et une class de model dans laquelle nous stockons datatables (accessibles par les deux controllers de vue enfant).

D'abord, vous créez un controller de vue conteneur avec votre contrôle segmenté (j'ai également ajouté un UIView qui définit essentiellement le cadre dans lequel seront placées les vues des controllers de vue enfant, juste pour mieux comprendre où placer cette vue):

 // ContainerViewController.h #import <UIKit/UIKit.h> @interface ContainerViewController : UIViewController @property (weak, nonatomic) IBOutlet UISegmentedControl *segmentedControl; @property (weak, nonatomic) IBOutlet UIView *childView; - (IBAction)changeChild:(id)sender; @end 

Et puis vous l'implémentez:

 // ContainerViewController.m #import "ContainerViewController.h" #import "FirstContainedViewController.h" #import "SecondContainedViewController.h" #import "MyModel.h" @interface ContainerViewController () { FirstContainedViewController *_controller0; SecondContainedViewController *_controller1; MyModel *_model; UIViewController __weak *_currentChildController; // let's keep track of the current } @end @implementation ContainerViewController @synthesize segmentedControl = _segmentedControl; @synthesize childView = _childView; - (void)dealloc { // let's release our child controllers _controller0 = nil; _controller1 = nil; // and release the model, too _model = nil; } // this is my own method to // 1. add the child view controller to the view controller hierarchy; // 2. do the appropriate notification (even though I don't use it, Apple says you should do this, so I will); and // 3. set the frame size to the appropriate size - (void)addChildToThisContainerViewController:(UIViewController *)childController { [self addChildViewController:childController]; [childController didMoveToParentViewController:self]; childController.view.frame = CGRectMake(0.0, 0.0, self.childView.frame.size.width, self.childView.frame.size.height); } - (void)viewDidLoad { [super viewDidLoad]; // let's create our model, our data _model = [[MyModel alloc] init]; // set the segmented index to point to the first one [self.segmentedControl setSelectedSegmentIndex:0]; // let's create our two controllers and provide each a pointer to our model _controller0 = [[FirstContainedViewController alloc] initWithNibName:@"FirstContainedView" bundle:nil]; _controller0.model = _model; _controller1 = [[SecondContainedViewController alloc] initWithNibName:@"SecondContainedView" bundle:nil]; _controller1.model = _model; // let's add them to the view controller hierarchy [self addChildToThisContainerViewController:_controller0]; [self addChildToThisContainerViewController:_controller1]; // let's add the currently selected controller as the "current child controller" and add it to our current view _currentChildController = [self.childViewControllers objectAtIndex:self.segmentedControl.selectedSegmentIndex]; [self.childView addSubview:_currentChildController.view]; } - (void)viewDidUnload { [self setChildView:nil]; [self setSegmentedControl:nil]; [super viewDidUnload]; // Release any retained subviews of the main view. } - (IBAction)segmentedControlValueChanged:(UISegmentedControl *)sender { UIViewController *oldChildController = _currentChildController; UIViewController *newChildController = [self.childViewControllers objectAtIndex:sender.selectedSegmentIndex]; UIViewAnimationOptions options; // let's change the animation based upon which segmented control you select ... you may change this as fits your desired UI if (sender.selectedSegmentIndex == 0) options = UIViewAnimationOptionTransitionFlipFromLeft; else options = UIViewAnimationOptionTransitionFlipFromRight; [self transitionFromViewController:oldChildController toViewController:newChildController duration:0.5 options:options animations:nil completion:nil]; _currentChildController = newChildController; } @end 

Mon model a juste deux éléments de données, un tableau d'objects, et une string, mais évidemment vous pouvez faire ce que vous voulez. Je vais juste montrer l'en-tête (car les détails d'implémentation sont sortingviaux et sans intérêt):

 // MyModel.h #import <Foundation/Foundation.h> @interface MyModel : NSObject @property (nonatomic, strong) NSMutableArray *listOfItems; @property (nonatomic, strong) NSSsortingng *displayText; - (MyModel *)init; @end 

Et les controllers de vue enfant sont pareillement sortingviaux:

 // FirstContainedViewController.h #import <UIKit/UIKit.h> @class MyModel; @interface FirstContainedViewController : UIViewController <UITableViewDataSource, UITableViewDelegate> @property (nonatomic, weak) MyModel *model; @end 

Et l'implémentation peut ressembler à ceci (c'est un exemple sortingvial, mais montre comment vous pouvez récupérer des informations à partir du model partagé):

 // FirstContainedViewController.m #import "FirstContainedViewController.h" #import "MyModel.h" @implementation FirstContainedViewController @synthesize model = _model; - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. } - (void)viewDidUnload { [super viewDidUnload]; // Release any retained subviews of the main view. } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return (interfaceOrientation == UIInterfaceOrientationPortrait); } #pragma mark - tableview data source delegate methods - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.model.listOfItems count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSSsortingng *cellIdentifier = @"fcvc"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; if (!cell) cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; cell.textLabel.text = [self.model.listOfItems objectAtIndex:indexPath.row]; return cell; } @end 

J'espère que cela vous donne une idée de la façon dont vous pourriez utiliser des controllers de vue séparés pour vos deux vues, comment basculer entre eux, rendre votre model accessible aux deux. C'est un exemple assez simple, mais fonctionnel. Il y a quelques optimizations que je pourrais suggérer, mais j'espère que c'est assez pour vous mettre dans la bonne direction.

Quelques options me sautent aux yeux:

  1. Si vous utilisez un UISegmentedControl , vous pouvez avoir deux objects UIView pour vos deux écrans (vous pouvez les créer dans le constructor de l'interface ou les comstackr par programme) et vous pouvez simplement masquer / afficher ou addSubview / removeFromSuperview que vous passez entre eux. Puisque toutes datatables seront gérées par votre controller de vue unique, c'est assez facile. Si les deux sous-vues deviennent vraiment compliquées, cependant, ce controller de vue unique pourrait devenir assez poilu. Mais devrait fonctionner correctement.

  2. Si vous vouliez des controllers de vue séparés, vous poursuivriez probablement le confinement des controllers de vue d'iOS 5 (voir Implémentation d'un controller de vue de conteneur dans la reference UIViewController ou voir WWDC 2011 session 102 ), bien que ce soit un peu plus compliqué. Vous pouvez stocker vos données en tant que propriétés du controller de vue de conteneur qui sont transmises ou référencées par les controllers enfants.

  3. Si vous n'êtes pas lié à l'interface de command segmentée, vous pouvez simplement utiliser le UITabBarController qui convient parfaitement à ce scénario (il s'agit en fait d'une permutation de l'option précédente, mais le controller de vue de conteneur, le controller de barre d'onglet dans ce cas). déjà écrit pour vous). Un controller de vue pour chacune des deux vues, et stocker datatables soit dans la class personnalisée UITabBarController , un singleton, un stockage persistant (comme les valeurs par défaut de l'user, datatables de base, sqlite), etc.

En traitant avec un segmentedControl c'est comme ça que je l'ai fait dans le passé. Vous pourriez être en mesure de créer une class de controller distincte pour gérer le model sous-jacent pour tout et nettoyer une partie de ce code, mais cela dépend vraiment de la distance que vous voulez prendre.

Pensez ModelViewController. Si ce ne sont vraiment que des vues que vous commutez entre, l'utilisation d'un UIViewController est appropriée, c'est ce que je fais habituellement avec les controls segmentés car il revient généralement à changer de vue pour le controller actuel. Le controller devrait certainement gérer l'IBAction pour le contrôle segmenté.

Les delegates et les sources de données étaient destinés à être extraits dans une class distincte quand cela avait du sens, et dans votre cas, c'est le cas. Je considérerais des classs séparées pour les différents delegates que vous utilisez, cela le nettoiera de manière significative tout en restant fidèle aux principes de design qu'Apple avait prévu. Vous pouvez conserver le délégué UITableView et la source de données set dans leur propre class, mais à part cela, créer une class séparée pour chaque délégué différent le nettoiera de manière significative.

Contrôleurs de vue séparée est probablement un moyen plus facile à faire. Je les préfère. Cela signifie également que vous pouvez faire en sorte que le controller de vue initial soit le parent des autres et disposer les différentes vues que vous avez et append vos controllers de vue enfant en tant que subvuews aux vues du parent au fur et à mesure de leurs besoins. Cela facilite également l'ajout d'animations personnalisées ou le relais à une date ultérieure.

À mon avis, votre meilleure option serait d'avoir trois View Controllers et de créer des Segments modaux entre eux. (Je suppose que vous utilisez des storyboards.) Vous auriez votre View Controller parent avec deux enfants (List & Map).

(1) Dans votre storyboard (qui a ParentViewController, ListViewController & MapViewController), créez une Segue Modale à partir de chaque button pour les VCs enfants. Donnez-leur des identifiants (showList & showMap fonctionnerait bien) et choisissez Transition: Flip Horizontal.

Mettre en place le protocole et les delegates: (Je vais vous montrer comment faire un, puis il suffit de le répéter.)

(2a) Dans ListViewController.h, ajoutez ci-dessus @interface:

 @class ListViewController; @protocol ListViewControllerDelegate - (void)listViewControllerDidFinish:(ListViewController *)controller; @end 

(2b) Ajoutez le délégué en tant que propriété:

 @property (weak, nonatomic) id <ListViewControllerDelegate> delegate; 

(3a) Dans ListViewController.m synthesize:

 @synthesize delegate; 

(3b) Et déléguer dans la méthode du button IBAction pour returnner:

 - (IBAction)flipBack:(id)sender { [self.delegate ListViewControllerDidFinish:self]; } 

(4) Dans ParentViewController.h, ajoutez tout d'abord #import "ListViewController.h" et à la fin de @interface <ListViewControllerDelegate>

(5a) Dans ParentViewController.m, ajoutez la méthode pour se conformer au protocole:

 - (void)listViewControllerFinish:(ListViewController *)controller { [self dismissModalViewControllerAnimated:YES]; } 

(5b) Puis définissez-le en tant que délégué dans prepareForSegue:

 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([[segue identifier] isEqualToSsortingng:@"showList"]) { [[segue destinationViewController] setDelegate:self]; } }