Doelstelling c - Wisselen tussen weergaven met één ViewController of meer?

Ik bouw een locatie-app die de gebruiker een aantal plaatsen om hem heen laat zien.
Ik heb een NearbyPlacesViewController met een segmentbesturingselement met twee knoppen "lijst" en "kaart"

Als gebruiker op "lijst" drukt, laat ik hem een ​​tabelweergave zien met de lijst met plaatsen om hem heen Als gebruiker op "kaart" drukt - het beeld draait rond en ik toon de gebruiker een kaartweergave met de plaatsen erop als pinnen.

In "list" gebruik ik ook een UISearchBar en een UISearchDisplayController om in de tabel te zoeken. View In "kaart" heb ik ook een aantal andere subweergaven naast de kaartweergave

Momenteel bewaar ik alle weergaven ( UITableView, MKMapView, UISearchBar en meer ...)
en de gedelegeerde methoden ( UITableViewDelegate, UITableViewDataSource, MKMapViewDelegate, UISearchDisplayDelegate en meer ..)
in de NearbyPlacesViewController .

Wanneer de gebruiker op de knop "kaart" klikt, verberg ik alle weergaven die relevant zijn voor de weergave "lijst" (tabelweergave, zoekbalk ...) en ik verwijder alle weergaven die relevant zijn voor de weergave "kaart" (kaartweergave, sommige andere subweergaven ...), gebruik ik vervolgens UIView transitionWithView om een ​​animatie tussen de afbeeldingen uit te draaien.

Alles werkt, maar het lijkt een beetje rommelig, en het resultaat is een grote NearbyPlacesViewController met veel code en gedelegeerde methoden.

Is het beter om dit te doen met aparte viewControllers?
En zo ja, hoe doe ik dat? maak ik ListViewController en MapViewController en plaats deze in een NearbyViewController?
Hoe deel ik het model tussen hen?

1

6 antwoord

Een paar opties springen op me uit:

  1. Als u een UISegmentedControl gebruikt, kunt u twee UIView -objecten hebben voor uw twee schermen (u kunt ze maken in interface builder of deze programmatisch opbouwen) en dan kon je gewoon verbergen/tonen of addSubview / removeFromSuperview terwijl je ertussen springt. Aangezien alle gegevens worden beheerd door uw single view-controller, is dit vrij eenvoudig. Als de twee subweergaven echt gecompliceerd worden, kan deze enkelbeeld-controller behoorlijk behaard worden. Maar zou goed moeten werken.

  2. Als u afzonderlijke view-controllers wilde, zou u waarschijnlijk de weergave-controller-containment van iOS 5 nastreven (zie Een containerviewcontroller implementeren in de UIViewController-referentie of zie WWDC 2011-sessie 102 ), hoewel dit iets gecompliceerder is. U kunt uw gegevens opslaan als eigenschappen van de containerezichtcontroller die worden doorgegeven aan of waarnaar wordt verwezen door de child controllers.

  3. Als u niet gehecht bent aan de gebruikersinterface voor gesegmenteerd beheer, kunt u gewoon de UITabBarController gebruiken die bij uitstek geschikt is voor dit scenario (het is in feite een permutatie van de vorige optie, maar de container view controller, de tabbalkcontroller in dit geval, is al voor u geschreven). Eén weergavecontroller voor elk van de twee weergaven, en sla de gegevens op in de UITabBarController aangepaste klasse, een singleton, enkele permanente opslag (zoals gebruikersstandaarden, kerngegevens, sqlite), enz.

3
toegevoegd
Momenteel gebruik ik optie 1, het probleem is: als ik bijvoorbeeld alleen de lijstweergave op een andere plaats (ander tabblad) wil weergeven, kan ik dezelfde viewController niet opnieuw gebruiken. Ik moet een nieuwe viewController maken zonder de kaartweergave, die veel code delen.
toegevoegd de auteur Eyal, de bron
@Eyal Ik weet niet zeker of ik de vraag begrijp: als je optie 1 doet, waarom heb je dan een andere weergavecontroller nodig zonder de kaartweergave? Waarom niet dezelfde weergavecontroller gebruiken, maar niet toestaan ​​dat de gebruiker naar de kaartweergave gaat (bijvoorbeeld het gesegmenteerde besturingselement uitschakelen, de kaart verborgen houden, enz.)? Of wil je een aparte controlemechanisme en vraag je je af hoe je de gegevens kunt delen?
toegevoegd de auteur Rob, de bron

Zodat u een idee krijgt van hoe de weergave van controllersystemen eruit kan zien (iOS 5), dit is een manier om het te doen. Het bestaat uit vier klassen, de container view-controller (wiens weergave de gesegmenteerde besturing heeft voor het schakelen tussen twee child view-controllers), twee willekeurige child view-controllers en een modelklasse waarin we de gegevens opslaan (waartoe toegang kan worden verkregen door de twee child view controllers).

Eerst maak je een containerzichtcontroller met je gesegmenteerde besturingselement (ik heb ook een UIView toegevoegd die in feite het frame definieert waar de weergaven van child view controllers worden geplaatst, gewoon om het gemakkelijker te maken om uit te zoeken waar die weergave moet worden geplaatst):

//  ContainerViewController.h

#import 

@interface ContainerViewController : UIViewController

@property (weak, nonatomic) IBOutlet UISegmentedControl *segmentedControl;
@property (weak, nonatomic) IBOutlet UIView *childView;

- (IBAction)changeChild:(id)sender;

@end

En dan implementeer je het:

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

Mijn model heeft slechts twee gegevenselementen, een reeks objecten en een tekenreeks, maar u kunt natuurlijk doen wat u wilt. Ik laat alleen de koptekst zien (omdat de implementatiedetails triviaal en oninteressant zijn):

//  MyModel.h

#import 

@interface MyModel : NSObject

@property (nonatomic, strong) NSMutableArray *listOfItems;
@property (nonatomic, strong) NSString *displayText;

- (MyModel *)init;

@end

En de controllers voor kindweergave zijn ook triviaal:

//  FirstContainedViewController.h

#import 

@class MyModel;

@interface FirstContainedViewController : UIViewController 

@property (nonatomic, weak) MyModel *model;

@end

En de implementatie kan er ongeveer zo uitzien (dit is een triviaal voorbeeld, maar laat zien hoe u informatie van het gedeelde model kunt ophalen):

//  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 NSString *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

Hopelijk geeft dit u een idee van hoe u afzonderlijke view controllers voor uw twee weergaven zou kunnen gebruiken, hoe u ertussen kunt schakelen, uw model toegankelijk maken voor beide. Dit is een vrij eenvoudig voorbeeld, maar het is functioneel. Er zijn enkele optimalisaties die ik zou kunnen voorstellen, maar hopelijk is dit voldoende om u in de goede richting te laten gaan.

3
toegevoegd
Hey @Rob, ik struikelde per ongeluk over dit antwoord van een ander antwoord van jou met betrekking tot een "segue die een instantie van weergave laadt". Hoe dan ook, ik was op zoek naar wat een goede manier is om met mijn situatie om te gaan, en het leek erop dat ik deze route moest gaan (ik ben niet echt zeker). Ik zoek uw advies over deze vraag die ik heb gepost stackoverflow.com/questions/14939510/… ... als je tijd hebt. Bedankt!
toegevoegd de auteur gdubs, de bron

Wanneer het gaat om een ​​gesegmenteerde besturing, zo heb ik het in het verleden gedaan. U kunt misschien een afzonderlijke controllerklasse maken om het onderliggende model voor alles af te handelen en een deel van die code op te schonen, maar dat hangt er echt van af hoe ver u wilt gaan.

2
toegevoegd

Denk aan ModelViewController. Als dit echt alleen weergaven zijn waar je heen en weer schakelt, is het gebruik van één UIViewController aangewezen, dat is wat ik meestal doe met gesegmenteerde besturingselementen, omdat dit meestal neerkomt op het omschakelen van de weergave voor de huidige controller. De controller moet beslist de IBAction voor de gesegmenteerde besturing gebruiken.

Gedelegeerden en gegevensbronnen moesten in een afzonderlijke klasse worden geëxtraheerd als het zinvol was, en in uw geval wel. Ik zou afzonderlijke klassen overwegen voor de verschillende afgevaardigden die je gebruikt, dit zal het aanzienlijk opruimen terwijl het ook dicht bij de ontwerpprincipes blijft die Apple bedoelde. U kunt de UITableView-gedelegeerde en databron samen in hun eigen klasse houden, maar behalve dat, maakt het maken van een afzonderlijke klasse voor elke verschillende gedelegeerde het aanzienlijk op.

1
toegevoegd
Ik heb nooit deze benadering van afzonderlijke klassen gebruikt voor tableViews delegate of datasource, altijd de parent viewController. Als ik dat doe, bewaar ik dan mijn model? zal het in de viewController zijn dat een exemplaar van de tabelView bevat, of in de klasse voor de gedelegeerde gegevensbron?
toegevoegd de auteur Eyal, de bron
Een MVC-purist zal u vertellen dat de controller het model moet beheren. In de praktijk kan ik een verwijzing naar het model doorgeven aan de gedelegeerden en gegevensbronnen (dus van het gedelegeerde zelflistmodel) in plaats van ernaar te verwijzen via de controller (zoals self.owningController.listModel). Technisch gezien kun je het op beide plaatsen doen, beide proberen en zien wat je beter voelt. Pas alleen op voor het behouden van cycli.
toegevoegd de auteur Andy Obusek, de bron

In my opinion, your best option would be to have three View Controllers and create Modal Segues between them. (I assume you're using storyboards.) You would have your parent View Controller with two children (List & Map).

(1) In your storyboard (that has ParentViewController, ListViewController & MapViewController) create a Modal Segue from each button to the child VCs. Give them identifiers (showList & showMap would work well) and choose Transition:Flip Horizontal.

Set up the protocol and delegates: (I'll show you how to do one, then just repeat it.)

(2a) Voeg in ListViewController.h boven @interface toe:

@class ListViewController;

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

(2b) Voeg de gedelegeerde toe als een eigenschap:

@property (weak, nonatomic) id  delegate;

(3a) In ListViewController.m synthetiseren:

@synthesize delegate;

(3b) En delegeren in de IBAction-knopmethode om terug te keren:

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

(4) In ParentViewController.h add at the very top #import "ListViewController.h" and on the end of @interface

(5a) Voeg in ParentViewController.m de methode toe om aan het protocol te voldoen:

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

(5b) Stel het vervolgens in als gedelegeerde in voorbereideForSegue:

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

Separate view controllers is waarschijnlijk een nettere manier om te gaan. Ik geef de voorkeur aan hen. Dit betekent ook dat u de initiële weergavecontroller de bovenliggende kunt maken voor de anderen en de verschillende weergaven kunt indelen en uw subcontroles voor kinderen kunt toevoegen als subvuews voor de weergaven van de ouder wanneer en wanneer ze nodig zijn. Maakt het ook gemakkelijker om een ​​aantal aangepaste animaties of relays op een later tijdstip toe te voegen.

1
toegevoegd