In questo tutorial, implementeremo una versione minimalista dell'interfaccia utente di Facebook / Stile del percorso. L'obiettivo sarà capire come utilizzare il contenimento del controller di visualizzazione per implementare il flusso personalizzato nella tua app.
I controller di visualizzazione sono una parte essenziale di qualsiasi applicazione iOS, indipendentemente da quanto siano piccoli, grandi, semplici o complessi. Forniscono la "logica della colla" tra il modello dati della tua app e l'interfaccia utente.
In generale, esistono due tipi di controller di visualizzazione:
Un controller contenitore può avere alcuni componenti visibili, ma fondamentalmente funziona come host per i controller di visualizzazione contenuto. I controller container servono a "trafficare" l'andirivieni dei controller di visualizzazione del contenuto.
UINavigationController
, UITabBarController
e UIPageViewController
sono esempi di controller di visualizzazione contenitori forniti con l'SDK di iOS. Considera come i tre sono diversi in termini di flussi applicativi che danno origine a. Il controller di navigazione è ottimo per un'app di tipo drill-down, in cui la selezione effettuata dall'utente in una schermata influisce sulle scelte che ha presentato nella schermata successiva. Il controller della barra delle linguette è ideale per le app con funzionalità indipendenti, consentendo un comodo comando semplicemente premendo un pulsante di tabulazione. Infine, il controller della visualizzazione di pagina presenta una metafora del libro, che consente all'utente di spostarsi avanti e indietro tra le pagine del contenuto.
La cosa fondamentale da tenere presente qui è che una vera schermata dei contenuti presentati attraverso uno di questi controller di visualizzazione contenitore deve essere gestita, sia in termini di dati da cui è derivata (il modello) che della presentazione sullo schermo (la vista), che sarebbe di nuovo il compito di un controller di visualizzazione. Ora stiamo parlando di controller di visualizzazione del contenuto. In alcune app, in particolare sull'iPad, poiché lo schermo più grande consente di visualizzare più contenuti contemporaneamente, è possibile che anche diverse visualizzazioni sullo schermo possano essere gestite autonomamente. Ciò richiede più controller di visualizzazione sullo schermo contemporaneamente. Tutto ciò implica che i controller di visualizzazione in un'app ben progettata dovrebbero essere implementati in modo gerarchico con i controller di visualizzazione del contenitore e del contenuto che riproducono i rispettivi ruoli.
Prima di iOS 5, non c'erano mezzi per dichiarare una relazione gerarchica (cioè genitore-figlio) tra due controller di vista e quindi nessun modo "corretto" di implementare un flusso di applicazioni personalizzato. Uno doveva accontentarsi dei tipi built-in, o farlo in modo casuale, che consisteva essenzialmente di incollare viste gestite da un controller di visualizzazione nella gerarchia della vista della vista gestita da un altro controller di visualizzazione. Ciò creerebbe incoerenze. Ad esempio, una vista finirebbe per essere nella gerarchia della vista di due controller senza che nessuno dei due controller riconosca l'altro, il che a volte porta a comportamenti strani. Il contenimento è stato introdotto in iOS 5 e leggermente modificato in iOS 6 e consente di formalizzare la nozione di controller di visualizzazione padre e figlio in una gerarchia. In sostanza, il corretto contenimento del controller di visualizzazione richiede che se la vista B è una sottoview (figlio) della vista A e se non sono sotto la gestione dello stesso controller di visualizzazione, allora il controllore di vista di B deve essere reso figlio di A view controller.
Potresti chiedere se ci sia un vantaggio concreto offerto dal contenimento del controller di visualizzazione oltre al vantaggio del design gerarchico di cui abbiamo discusso. La risposta è si. Tieni presente che quando un controller di visualizzazione appare sullo schermo o se ne va, potremmo aver bisogno di impostare o distruggere risorse, pulire, recuperare o salvare informazioni dal / al file system. Sappiamo tutti dei richiami all'aspetto. Dichiarando esplicitamente la relazione genitore-figlio, ci assicuriamo che il controllore genitore inoltrerà i callback ai suoi figli ogni volta che uno si accende o esce dallo schermo. Devono essere inoltrati anche i callback di rotazione. Quando l'orientamento cambia, tutti i controller di visualizzazione sullo schermo devono sapere in modo che possano adattare i loro contenuti in modo appropriato.
Cosa implica tutto ciò in termini di codice? I controller di vista hanno un NSArray
proprietà chiamato childViewControllers
e le nostre responsabilità includono l'aggiunta e la rimozione dei controller di vista figlio da e verso questo array nel genitore chiamando metodi appropriati. Questi metodi includono addChildViewController
(chiamato il genitore) e removeFromParentViewController
(chiama il bambino) quando cerchiamo di creare o distruggere la relazione genitore-figlio. Ci sono anche un paio di messaggi di notifica inviati al controller di visualizzazione figlio all'inizio e alla fine del processo di aggiunta / rimozione. Questi sono willMoveToParentViewController:
e didMoveToParentViewController:
, inviato con il controller genitore appropriato come argomento. L'argomento è zero
, se il bambino viene rimosso. Come vedremo, uno di questi messaggi verrà inviato per noi automaticamente mentre l'altro sarà nostra responsabilità inviare. Questo dipenderà dal fatto che stiamo aggiungendo o rimuovendo il bambino. Studieremo la sequenza esatta a breve quando implementeremo le cose nel codice. Il controllore figlio può rispondere a queste notifiche implementando i metodi corrispondenti se deve fare qualcosa in preparazione di questi eventi.
Abbiamo anche bisogno di aggiungere / rimuovere le viste associate al controllore della vista figlio alla gerarchia del genitore, usando metodi come addSubview:
o removeFromSuperview
), compresa l'esecuzione di eventuali animazioni di accompagnamento. C'è un metodo di convenienza (-) transitionFromViewController: toViewController: Durata: opzioni: animazioni: esecuzione:
ciò ci consente di semplificare il processo di scambio dei controller di visualizzazione figlio sullo schermo con animazioni. Vedremo i dettagli esatti quando scriviamo il codice - che è il prossimo!
Crea una nuova app iOS in Xcode basata su "Applicazione vuotamsgstr "Crea un app iOS con ARC abilitato VCContainmentTut.
Creare un nuovo progettoCrea una nuova classe chiamata RootController
. Fatelo a UIViewController
sottoclasse. Assicurarsi che le caselle di controllo siano deselezionate. Questa sarà la sottoclasse del controller della vista contenitore.
Sostituisci il codice RootViewController.h con il seguente.
#importare@interface RootController: UIViewController // (1) - (id) initWithViewControllers: (NSArray *) viewControllers andMenuTitles: (NSArray *) titoli; // (2) @end
Il nostro controller contenitore avrà una vista tabella che funziona come il nostro menu, e toccando qualsiasi cella sostituirà il controller vista attualmente visibile da quello selezionato tramite il tocco dell'utente.
Riferendosi ai punti nel codice,
Diamo uno sguardo avanti per vedere come sarà il nostro prodotto finito in modo da avere un'immagine mentale per associare l'implementazione con.
Aiuterà a capire che il nostro controller di visualizzazione contenitori è abbastanza simile a un controller di visualizzazione a schede. Ogni voce nel menu corrisponde a un controller di visualizzazione indipendente. La differenza tra i nostri "menu scorrevole"Il controller e il controller di tabulazione sono visivi per la maggior parte.menu scorrevole"è un po 'improprio perché è in realtà il controller di visualizzazione del contenuto che scorre per nascondere o rivelare il menu sottostante.
Passando all'implementazione, sostituire tutto il codice in RootController.m con il seguente codice.
#define kExposedWidth 200.0 #define kMenuCellID @ "MenuCell" #import "RootController.h" @interface RootController () @property (nonatomic, strong) Menu UITableView *; @property (nonatomic, strong) NSArray * viewControllers; @property (nonatomic, strong) NSArray * menuTitles; @property (nonatomic, assign) NSInteger indexOfVisibleController; @property (nonatomic, assign) BOOL isMenuVisible; @end @implementation RootController - (id) initWithViewControllers: (NSArray *) viewControllers andMenuTitles: (NSArray *) menuTitles if (self = [super init]) NSAssert (self.viewControllers.count == self.menuTitles.count, @ "Ci deve essere uno e un solo titolo di menu corrispondente a ogni controller di visualizzazione!"); // (1) NSMutableArray * tempVCs = [NSMutableArray arrayWithCapacity: viewControllers.count]; self.menuTitles = [menuTitles copy]; for (UIViewController * vc in viewControllers) // (2) if (! [vc isMemberOfClass: [classe UINavigationController]]) [tempVCs addObject: [[UINavigationController alloc] initWithRootViewController: vc]]; else [tempVCs addObject: vc]; UIBarButtonItem * revealMenuBarButtonItem = [[UIBarButtonItem alloc]] initWithTitle: @ Stile "Menu": UIBarButtonItemStylePlain target: self action: @selector (toggleMenuVisibility :)]; // (3) UIViewController * topVC = ((UINavigationController *) tempVCs.lastObject) .topViewController; topVC.navigationItem.leftBarButtonItems = [@ [revealMenuBarButtonItem] arrayByAddingObjectsFromArray: topVC.navigationItem.leftBarButtonItems]; self.viewControllers = [tempVCs copy]; self.menu = [[UITableView alloc] init]; // (4) self.menu.delegate = self; self.menu.dataSource = self; return self; - (void) viewDidLoad [super viewDidLoad]; [self.menu registerClass: [UITableViewCell class] forCellReuseIdentifier: kMenuCellID]; self.menu.frame = self.view.bounds; [self.view addSubview: self.menu]; self.indexOfVisibleController = 0; UIViewController * visibleViewController = self.viewControllers [0]; visibleViewController.view.frame = [self offScreenFrame]; [self addedChildViewController: visibleViewController]; // (5) [self.view addSubview: visibleViewController.view]; // (6) self.isMenuVisible = YES; [self adjustContentFrameAccordingToMenuVisibility]; // (7) [self.viewControllers [0] didMoveToParentViewController: self]; // (8) - (void) toggleMenuVisibility: (id) sender // (9) self.isMenuVisible =! Self.isMenuVisible; [self adjustContentFrameAccordingToMenuVisibility]; - (void) adjustContentFrameAccordingToMenuVisibility // (10) UIViewController * visibleViewController = self.viewControllers [self.indexOfVisibleController]; CGSize size = visibleViewController.view.frame.size; if (self.isMenuVisible) [UIView animateWithDuration: 0.5 animazioni: ^ visibleViewController.view.frame = CGRectMake (kExposedWidth, 0, size.width, size.height); ]; else [UIView animateWithDuration: 0.5 animazioni: ^ visibleViewController.view.frame = CGRectMake (0, 0, size.width, size.height); ]; - (void) replaceVisibleViewControllerWithViewControllerAtIndex: (NSInteger) indice // (11) if (index == self.indexOfVisibleController) return; UIViewController * incomingViewController = self.viewControllers [index]; incomingViewController.view.frame = [self offScreenFrame]; UIViewController * outgoingViewController = self.viewControllers [self.indexOfVisibleController]; CGRect visibleFrame = self.view.bounds; [outgoingViewController willMoveToParentViewController: nil]; // (12) [self addedChildViewController: incomingViewController]; // (13) [[UIApplication sharedApplication] beginIgnoringInteractionEvents]; // (14) [self transitionFromViewController: outgoingViewController // (15) toViewController: incomingViewController durata: 0,5 opzioni: 0 animazioni: ^ outgoingViewController.view.frame = [self offScreenFrame]; completamento: ^ (BOOL terminato) [UIView animateWithDuration: 0.5 animazioni: ^ [outgoingViewController.view removeFromSuperview]; [self.view addSubview: incomingViewController.view]; incomingViewController.view.frame = visibleFrame; [[UIApplication sharedApplication] endIgnoringInteractionEvents]; // (16)]; [incomingViewController didMoveToParentViewController: self]; // (17) [outgoingViewController removeFromParentViewController]; // (18) self.isMenuVisible = NO; self.indexOfVisibleController = index; ]; // (19): - (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView return 1; - (NSInteger) tableView: (UITableView *) tableView numberOfRowsInSection: (NSInteger) section return self.menuTitles.count; - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: kMenuCellID]; cell.textLabel.text = self.menuTitles [indexPath.row]; cella di ritorno; - (void) tableView: (UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *) indexPath [self replaceVisibleViewControllerWithViewControllerAtIndex: indexPath.row]; - (CGRect) offScreenFrame return CGRectMake (self.view.bounds.size.width, 0, self.view.bounds.size.width, self.view.bounds.size.height); @fine
Ora per una spiegazione del codice. Le parti che ho evidenziato per enfatizzare sono particolarmente rilevanti per l'implementazione del contenimento.
UIViewController
e NSString
tipi rispettivamente. Potresti considerare di farlo. Nota che stiamo mantenendo gli array per ognuno di questi, chiamato viewControllers
, e menuTitles
.viewDidLoad
, dopo aver configurato e aggiunto la vista tabella del menu alla vista del nostro controller di root, introduciamo nella nostra app il primo controller di visualizzazione in viewControllers
schieramento. Inviando il addChildViewController:
messaggio al nostro root controller, eseguiamo la nostra prima responsabilità relativa al contenimento. Dovresti sapere che questo causa il messaggio willMoveToParentViewController:
essere chiamato sul controller figlio.se stesso
, l'istanza RootController, come argomento. Nel nostro controller figlio, possiamo implementare questo metodo se necessario.adjustContentFrameAccordingToMenuVisibility
ci consente di regolare la cornice del controller della vista del contenuto per dirci se il menu è nascosto o meno. Se sì, si sovrappone alla superview. Altrimenti, viene spostato a destra di kExposedWidth
. L'ho impostato su 200 punti.replaceVisibleViewControllerWithViewControllerAtIndex
ci consente di scambiare i controller di visualizzazione e le viste corrispondenti dalla gerarchia. Per estrarre la nostra animazione, che consiste nel far scorrere il controller della vista sostituito fuori dallo schermo verso destra e quindi introdurre il controller sostitutivo dalla stessa posizione, definiamo alcuni riquadri rettangolari.zero
. Una volta completato questo passaggio, questo controller di visualizzazione cesserà di ricevere l'aspetto e le richiamate di rotazione dal genitore. Questo ha senso perché non è più una parte attiva dell'app.didMoveToParentViewController
messaggio con se stesso come argomento.removeFromParentViewController
Messaggio. Dovresti saperlo didMoveToParentViewController:
con zero
come argomento viene inviato per te.-tableView: didSelectRowAtIndexPath:
metodo.Potresti aver trovato la sequenza di chiamate relative al contenimento del controller un po 'confusa. Aiuta a riassumere.
addChildViewController:
sul genitore con il bambino come argomento. Questo causa il messaggio willMoveToParentViewController:
da inviare al bambino con il genitore come argomento.didMoveToParentViewController:
sul bambino con il genitore come argomento.willMoveToParentViewController:
sul bambino con zero
come argomento.removeFromParentViewController
al bambino. Causa il messaggio didMoveToParentViewController
con zero
come argomento da inviare al bambino a tuo nome.Proviamo i diversi tipi di controller di visualizzazione aggiunti al nostro controller di root! Crea una nuova sottoclasse di UIViewController
chiamato ViewController
, mantenendo deselezionate le opzioni.
Sostituisci il codice in ViewController.m con il seguente codice.
#import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void) willMoveToParentViewController: (UIViewController *) parent NSLog (@ "% @ (% p) -% @", NSStringFromClass ([auto classe] ), self, NSStringFromSelector (_cmd)); - (void) didMoveToParentViewController: (UIViewController *) parent NSLog (@ "% @ (% p) -% @", NSStringFromClass ([self class]), self, NSStringFromSelector (_cmd)); - (void) viewWillAppear: (BOOL) animato [super viewWillAppear: animated]; NSLog (@ "% @ (% p) -% @", NSStringFromClass ([auto classe]), self, NSStringFromSelector (_cmd)); - (void) viewDidAppear: (BOOL) animato [super viewDidAppear: animato]; NSLog (@ "% @ (% p) -% @", NSStringFromClass ([auto classe]), self, NSStringFromSelector (_cmd)); - (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation duration: (NSTimeInterval) duration [super willRotateToInterfaceOrientation: toInterfaceOrientation duration: duration]; NSLog (@ "% @ (% p) -% @", NSStringFromClass ([auto classe]), self, NSStringFromSelector (_cmd)); - (void) haRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation [super didRotateFromInterfaceOrientation: fromInterfaceOrientation]; NSLog (@ "% @ (% p) -% @", NSStringFromClass ([auto classe]), self, NSStringFromSelector (_cmd)); @fine
Non c'è nulla di speciale nel nostro controller di visualizzazione, tranne che abbiamo sovrascritto i vari callback in modo da poterli registrare ogni volta che il nostro ViewController l'istanza diventa un figlio per il nostro controller di root e si verifica un evento di comparsa o di rotazione.
In tutto il codice precedente, _cmd
si riferisce al selettore corrispondente al metodo in cui si trova la nostra esecuzione. NSStringFromSelector ()
lo converte in una stringa. Questo è un modo semplice e veloce per ottenere il nome del metodo corrente senza doverlo digitare manualmente.
Lanciamo un controller di navigazione e un controller di tabulazione nel mix. Questa volta useremo Storyboard.
Crea un nuovo file e sotto iOS> Interfaccia utente, scegliere storyboard. Imposta la famiglia di dispositivi su i phone, e nominalo NavStoryBoard.
Dal libreria di oggetti, trascinare e rilasciare a Controller di navigazione oggetto nella tela. Trascina e rilascia a elemento pulsante bar nella parte sinistra della barra di navigazione in controller di visualizzazione tabella designato come "Root View ControllerMsgstr "Questo contiene la vista tabella nell'area di disegno. Assegna un nome qualsiasi.SinistraIl suo scopo è quello di verificare il codice che abbiamo scritto per fare in modo che il pulsante nascondi / rivela della barra dei menu sia posizionato come il pulsante più a sinistra sulla barra di navigazione, spingendo a destra qualsiasi pulsante già presente. Visualizza controller istanza e posizionarlo a destra del controller intitolato "Root View Controller"nella tela.
Clicca dove dice "Vista tabella"al centro del secondo controller e nel ispettore di attributi cambia il contenuto da "Prototipo dinamico" a "Celle statiche".
Modifica del tipo di contenuto della cella da dinamico a staticoCiò causerà la visualizzazione di tre celle di visualizzazione tabella statiche nel builder dell'interfaccia. Elimina tutte tranne una di queste celle di visualizzazione tabella e mentre tieni premuto Controllo, fare clic e trascinare dalla cella rimanente al controller della vista all'estrema destra e rilasciare. Seleziona "Spingere" sotto Segmento di selezione. Tutto ciò fa sì che segua il controller della vista a destra quando tocchi la cella solitaria dalla vista tabella. Se vuoi, puoi lasciare un UILabel sulla cella della tabella per dargli del testo. Lo storyboard dovrebbe essere simile alla foto qui sotto.
NavStoryBoardInfine, aggiungiamo un controller della barra delle linguette. Proprio come hai fatto in precedenza, crea un storyboard file e chiamalo TabStoryBoard. Trascina e rilascia a controller della barra delle linguette oggetto dal libreria di oggetti nella tela. Viene preconfigurato con due schede e, se lo desideri, puoi cambiare il colore di sfondo dei due controller a schede, facendo clic sulla vista corrispondente a entrambi i controller di visualizzazione e modificando il "sfondo"opzione nel Ispettore degli attributi. In questo modo, è possibile verificare che la selezione del controller di visualizzazione tramite la scheda funzioni correttamente.
Il tuo tabellone dovrebbe assomigliare a questo.Ora è il momento di impostare tutto in AppDelegate.
Sostituisci il codice in AppDelegate.m con il seguente codice.
#import "AppDelegate.h" #import "RootController.h" #import "ViewController.h" @implementation AppDelegate - (BOOL) application: (applicazione UIApplication *) didFinishLaunchingWithOptions: (NSDictionary *) launchOptions self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bound]]; UIStoryboard * tabStoryBoard = [UIStoryboard StoryboardWithName: @ "TabStoryboard" bundle: nil]; UIStoryboard * navStoryBoard = [UIStoryboard storyboardWithName: @ "NavStoryboard" bundle: nil]; UINavigationController * navController = [navStoryBoard instantiateViewControllerWithIdentifier: @ "Nav Controller"]; UITabBarController * tabController = [tabStoryBoard instantiateViewControllerWithIdentifier: @ "Tab Controller"]; ViewController * redVC, * greenVC; redVC = [[ViewController alloc] init]; greenVC = [[ViewController alloc] init]; redVC.view.backgroundColor = [UIColor redColor]; greenVC.view.backgroundColor = [UIColor greenColor]; RootController * menuController = [[RootController alloc] initWithViewControllers: @ [tabController, redVC, greenVC, navController] andMenuTitles: @ [@ "Tab", @ "Red", @ "Green", @ "Nav"]]; self.window.rootViewController = menuController; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return YES;
Tutto ciò che abbiamo fatto è stato creare istanze di ViewController
e istanziare la navigazione e il controller delle schede dai due storyboard. Li abbiamo passati in fila al nostro RootController
l'istanza Questo è il controller contenitore che abbiamo implementato all'inizio. Lo abbiamo fatto insieme a una serie di stringhe per nominare i controller della vista nel menu. Ora definiremo semplicemente la nostra istanza Root Controller inizializzata come quella della finestra RootViewController
proprietà.
Costruisci ed esegui l'app. Hai appena implementato il contenimento dei container! Toccare le varie celle della tabella nel menu per sostituire la diapositiva visibile con quella nuova che scorre da destra. Si noti come, per l'istanza del controller di navigazione (denominata "NAVC"nel menu), il"Sinistra"il pulsante si è spostato di un posto a destra e il pulsante della barra dei menu ha assunto la posizione più a sinistra. Puoi cambiare l'orientamento in orizzontale e verificare che tutto appaia corretto.
Schermate del simulatoreIn questo tutorial introduttivo, abbiamo esaminato il modo in cui il contenimento del controller di visualizzazione è implementato in iOS 6. Abbiamo sviluppato una versione semplice di un'interfaccia per app personalizzata che ha guadagnato molta popolarità e viene spesso vista in app molto utilizzate come Facebook e Path. La nostra implementazione è stata la più semplice possibile, quindi siamo stati in grado di sezionarlo facilmente e ottenere le basi giuste. Esistono molte sofisticate implementazioni open source di questo tipo di controller che puoi scaricare e studiare. Si apre una rapida ricerca su Google JASidePAnels
e SWRevealViewController
, tra gli altri.
Ecco alcune idee su cui puoi lavorare.
Una co