I denne opplæringen implementerer vi en minimalistisk versjon av brukergrensesnittet Facebook / Path-stil. Målet vil være å forstå hvordan du bruker visningskontrollenes inneslutning for å implementere tilpasset strøm i appen din.
Vis kontroller er en viktig del av et hvilket som helst iOS-program, uansett hvor lite, stort, enkelt eller komplekst. De gir "limlogikken" mellom datamodellen til appen din og brukergrensesnittet.
I bred grad er det to typer visningskontroller:
En containerkontroller kan ha noen synlig komponent av seg selv, men fungerer i utgangspunktet som vert for innholdsvisningskontrollere. Containerkontrollere tjener til å "trafikkere" kommendene og gåttene til innholdsvisningskontrollere.
UINavigationController
, UITabBarController
og UIPageViewController
er eksempler på beholdervisningskontrollere som sender med iOS SDK. Vurder hvordan de tre er forskjellige når det gjelder applikasjonsflytene som de gir opphav til. Navigasjonskontrolleren er flott for en applikasjon med drill-down-typen, der valget brukeren gjør på en skjerm, påvirker hvilke valg han presenteres på neste skjermbilde. Kontrollpanelet for kontrollpanelet er flott for apper med uavhengige funksjoner, slik at du enkelt kan bytte ved å trykke på en knappeknapp. Endelig presenterer sidevisningscontrolleren en bokmetafor, slik at brukeren kan bla frem og tilbake mellom sider med innhold.
Den viktigste tingen å huske på her er at en faktisk skjermfull av innhold som presenteres gjennom noen av disse beholdervisningskontrollene selv må styres, både når det gjelder dataene den kommer fra (modellen) og skjermpresentasjonen (visningen), som igjen ville være jobben til en visningskontroller. Nå snakker vi om innholdsvisningskontrollere. I enkelte apper, spesielt på iPad fordi den større skjermen gjør det mulig å vise flere innhold på en gang, kan det hende at ulike visninger på skjermen kanskje må administreres uavhengig. Dette krever flere visningskontrollere på skjermen samtidig. Alt dette innebærer at visningskontrollerne i en godt designet app skal implementeres på en hierarkisk måte med både container- og innholdsvisningskontrollere som spiller sine respektive roller.
Før iOS 5 var det ikke noe middel til å erklære et hierarkisk (dvs. foreldre-barn) forhold mellom to visningskontrollere og derfor ikke en "riktig" måte å implementere en egendefinert applikasjonsflyt på. Man måtte enten gjøre med de innebygde typene, eller gjøre det på en tilfeldig måte, som i utgangspunktet besto av stikkende visninger administrert av en visningskontroller i visningshierarkiet av visningen som administreres av en annen visningsregulator. Dette ville skape uoverensstemmelser. For eksempel vil en visning ende opp med å være i visningshierarkiet til to kontrollere uten at noen av disse kontrollerne anerkjenner den andre, noe som noen ganger fører til merkelig oppførsel. Inneslutning ble introdusert i IOS 5 og raffinert litt i IOS 6, og det tillater at forestillingen om foreldre og barnesyn kontrollører i et hierarki blir formalisert. I hovedsak krever korrekt innstilling av kontrollkontrollen at hvis visning B er et undervisning (barn) til visning A, og hvis de ikke er under ledelse av samme visningsregulator, må Bs visningsstyrer gjøres til A's view-kontrollørens barn.
Du kan spørre om det er noen konkret fordel som tilbys ved å se kontrollenhetens inneslutning utover fordelen av det hierarkiske designet vi diskuterte. Svaret er ja. Husk at når en visningscontroller kommer på skjermen eller går bort, kan det hende at vi må sette opp eller rive ned ressurser, rydde opp, hente eller lagre informasjon fra / til filsystemet. Vi vet alle om utseende tilbakekallinger. Ved å forklare foreldre-barn-forholdet, sørger vi for at foreldrekontrolleren sender videre tilbakemeldinger til sine barn når en kommer på eller går av skjermen. Rotasjons tilbakeringinger må også videresendes. Når orienteringen endres, må alle visningsstyrerne på skjermen vite slik at de kan tilpasse innholdet på riktig måte.
Hva betyr alt dette, når det gjelder kode? Se kontroller har en NSArray
eiendom kalt childViewControllers
og vårt ansvar omfatter å legge til og fjerne barnvisningskontrollere til og fra denne gruppen i foreldrene ved å ringe til rette metoder. Disse metodene inkluderer addChildViewController
(oppfordret til foreldrene) og removeFromParentViewController
(kalt barnet) når vi prøver å gjøre eller bryte foreldre-barn forholdet. Det er også et par meldingsmeldinger som sendes til barnevisekontrolleren ved starten og slutten av tilleggs / fjerningsprosessen. Disse er willMoveToParentViewController:
og didMoveToParentViewController:
, sendt med riktig foreldrekontrollør som argument. Argumentet er nil
, hvis barnet blir fjernet. Som vi ser, blir en av disse meldingene sendt automatisk til oss mens den andre har ansvaret for å sende. Dette vil avhenge av om vi legger til eller fjerner barnet. Vi vil studere den nøyaktige sekvensen snart når vi implementerer ting i kode. Barnet kontrolleren kan svare på disse varslene ved å implementere de tilsvarende metodene hvis den trenger å gjøre noe under forberedelsen av disse hendelsene.
Vi må også legge til / fjerne visninger som er tilknyttet barnvisningen til foreldrenes hierarki, ved hjelp av metoder som addSubview:
eller removeFromSuperview
), inkludert å utføre noen medfølgende animasjoner. Det er en praktisk metode (-) transitionFromViewController: toViewController Varighet alternativer: animasjoner: ferdigstillelse:
som gir oss mulighet til å strømlinjeforme prosessen med å bytte barnvisningskontrollere på skjermen med animasjoner. Vi ser på de nøyaktige detaljene når vi skriver koden - som er neste!
Opprett en ny iOS-app i Xcode basert på "Tom søknad"mal. Gjør det til en iOS-app med ARC-aktivert. Ring det VCContainmentTut.
Opprette et nytt prosjektOpprett en ny klasse som heter RootController
. Gjør det til en UIViewController
underklasse. Kontroller at eventuelle avkrysningsbokser er deaktivert. Dette blir vår undervisningsgruppe for kontroller av kontroller.
Erstatt koden i RootViewController.h med følgende.
#importere@interface RootController: UIViewController // (1) - (id) initWithViewControllers: (NSArray *) viewControllers andMenuTitles: (NSArray *) titler; // (2) @end
Vår containerkontroller har en tabellvisning som fungerer som vår meny, og ved å trykke på en hvilken som helst celle, erstatter den nåværende synlige kontrolleren av den som er valgt gjennom brukerens trykk.
Henvisning til punktene i koden,
La oss ta en titt fremover for å se hva vårt ferdige produkt vil se ut slik at du har et mentalt bilde for å knytte implementeringen med.
Det vil bidra til å innse at vår containervisningskontroller er ganske lik en tabulator. Hvert element i menyen tilsvarer en uavhengig visningskontroller. Forskjellen mellom vår "skyvemeny"kontrolleren og fanen er visuelt for det meste. Uttrykket"skyvemeny"er litt misvisende fordi det faktisk er innholdsvisningskontrollen som glir for å skjule eller avsløre menyen under.
Fortsett til implementeringen, erstatt all koden i RootController.m med følgende kode.
#define kExposedWidth 200.0 #define kMenuCellID @ "MenuCell" #import "RootController.h" @interface RootController () @property (ikkeatomisk, sterk) UITableView * -menyen; @property (nonatomic, strong) NSArray * viewControllers; @property (nonatomic, strong) NSArray * menyTitler; @property (ikkeatomisk, tilordne) NSInteger indexOfVisibleController; @property (nonatomic, assign) BOOL erMenuVisible; @end @implementation RootController - (id) initWithViewControllers: (NSArray *) viewControllers andMenuTitles: (NSArray *) menyTitler hvis (selv = [super init]) NSAssert (self.viewControllers.count == self.menuTitles.count, @ "Det må være en og eneste meny tittel som tilsvarer hver visningskontroller!"); // (1) NSMutableArray * tempVCs = [NSMutableArray arrayWithCapacity: viewControllers.count]; self.menuTitles = [menuTitles copy]; for (UIViewController * vc i viewControllers) // (2) if (! [vc isMemberOfClass: [UINavigationController class]]) [tempVCs addObject: [[UINavigationController alloker] initWithRootViewController: vc]]; ellers [tempVCs addObject: vc]; UIBarButtonItem * revealMenuBarButtonItem = [[UIBarButtonItem alloc] initWithTitle: @ "Meny" stil: UIBarButtonItemStylePlain mål: selvhandling: @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; returner selv; - (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 addChildViewController: visibleViewController]; // (5) [self.view addSubview: visibleViewController.view]; // (6) self.isMenuVisible = JA; [selvjusteringContentFrameAccordingToMenuVisibility]; // (7) [self.viewControllers [0] didMoveToParentViewController: selv]; // (8) - (void) toggleMenuVisibility: (id) sender // (9) self.isMenuVisible =! Self.isMenuVisible; [selvjusteringContentFrameAccordingToMenuVisibility]; - (void) adjustContentFrameAccordingToMenuVisibility // (10) UIViewController * visibleViewController = self.viewControllers [self.indexOfVisibleController]; CGSize size = visibleViewController.view.frame.size; hvis (self.isMenuVisible) [UIView animateWithDuration: 0.5 animasjoner: ^ visibleViewController.view.frame = CGRectMake (kExposedWidth, 0, size.width, size.height); ]; ellers [UIView animateWithDuration: 0.5 animasjoner: ^ visibleViewController.view.frame = CGRectMake (0, 0, size.width, size.height); ]; - (void) replaceVisibleViewControllerWithViewControllerAtIndex: (NSInteger) indeks // (11) if (index == self.indexOfVisibleController) returnere; UIViewController * incomingViewController = self.viewControllers [indeks]; incomingViewController.view.frame = [self offScreenFrame]; UIViewController * outgoingViewController = self.viewControllers [self.indexOfVisibleController]; CGRect visibleFrame = self.view.bounds; [outgoingViewController willMoveToParentViewController: null]; // (12) [self addChildViewController: incomingViewController]; // (13) [[UIApplication sharedApplication] beginIgnoringInteractionEvents]; // (14) [self transitionFromViewController: outgoingViewController // (15) toViewController: incomingViewController varighet: 0.5 alternativer: 0 animasjoner: ^ outgoingViewController.view.frame = [self offScreenFrame]; fullføring: ^ (BOOL ferdig) [UIView animateWithDuration: 0.5 animasjoner: ^ outgoingViewController.view removeFromSuperview]; [self.view addSubview: incomingViewController.view]; incomingViewController.view.frame = visibleFrame; [[UIApplication sharedApplication] endIgnoringInteractionEvents]; // (16)]; [innkommendeViewController didMoveToParentViewController: selv]; // (17) [outgoingViewController removeFromParentViewController]; // (18) self.isMenuVisible = NO; self.indexOfVisibleController = index; ]; // (19): - (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView return 1; - (NSInteger) tableView: (UITableView *) tableView numberOfRowsInSection: (NSInteger) seksjonen return self.menuTitles.count; - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath UITableViewCell * celle = [tableView dequeueReusableCellWithIdentifier: kMenuCellID]; cell.textLabel.text = self.menuTitles [indexPath.row]; returcelle; - (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); @slutt
Nå for en forklaring av koden. Delene som jeg har uthevet for vektlegging, er spesielt relevante for innlemmingen.
UIViewController
og NSString
typer henholdsvis. Du kan vurdere å gjøre det. Vær oppmerksom på at vi opprettholder arrayer for hver av disse, kalt viewControllers
, og menuTitles
.viewDidLoad
, Etter at du har konfigurert og lagt til menybordvisningen til rotorkontrollørens visning, innleder vi inn i vår app den første visningskontrolleren i viewControllers
matrise. Ved å sende addChildViewController:
melding til vår rotorkontroller, utfører vi vårt første inneslutningsrelaterte ansvar. Du bør vite at dette fører til meldingen willMoveToParentViewController:
å bli kalt barnets kontroller.selv-
, RootController-forekomsten, som argumentet. I vår barns kontroller kan vi implementere denne metoden hvis vi trenger det.adjustContentFrameAccordingToMenuVisibility
lar oss justere innholdsvisningskontrollens ramme for å fortelle oss om menyen er skjult eller ikke. Hvis ja, overlapper det tilsynet. Ellers skiftes det til høyre ved kExposedWidth
. Jeg har satt det til 200 poeng.replaceVisibleViewControllerWithViewControllerAtIndex
tillater oss å bytte ut se kontroller og tilhørende visninger fra hierarkiet. For å trekke av animasjonen vår, som består av å skyve den erstattede visningskontrollen til skjermen til høyre og deretter ta inn erstatningsenheten fra samme sted, definerer vi noen rektangulære rammer.nil
. Når vi har fullført dette trinnet, vil denne visningskontrolleren slutte å motta utseende og rotasjonsoppringninger fra foreldrene. Dette gir mening fordi det ikke lenger er en aktiv del av appen.didMoveToParentViewController
melding med selv- som argumentet.removeFromParentViewController
budskap. Du burde vite det didMoveToParentViewController:
med nil
som et argument blir sendt for deg.-Tableview: didSelectRowAtIndexPath:
metode.Du har kanskje funnet sekvensen av samtaler som er relatert til kontrollerens inneslutning, litt forvirrende. Det hjelper å oppsummere.
addChildViewController:
på foreldrene med barnet som argumentet. Dette forårsaker meldingen willMoveToParentViewController:
å bli sendt til barnet med foreldrene som argumentet.didMoveToParentViewController:
på barnet med foreldrene som argumentet.willMoveToParentViewController:
på barnet med nil
som argumentet.removeFromParentViewController
til barnet. Årsaken til meldingen didMoveToParentViewController
med nil
som argumentet som skal sendes til barnet på dine vegne.La oss teste de forskjellige typer visningskontrollere som er lagt til vår rotkontroll! Opprett en ny underklasse av UIViewController
kalt ViewController
, holde eventuelle valg ukontrollert.
Bytt koden i ViewController.m med følgende kode.
#import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void) willMoveToParentViewController: (UIViewController *) forelder NSLog (@ "% @ (% p) -% @", NSStringFromClass ([selvklasse] ), selv, NSStringFromSelector (_cmd)); - (void) didMoveToParentViewController: (UIViewController *) forelder NSLog (@ "% @ (% p) -% @", NSStringFromClass ([selvklasse)), selv, NSStringFromSelector (_cmd)); - (void) viewWillAppear: (BOOL) animert [super viewWillAppear: animated]; NSLog (@ "% @ (% p) -% @", NSStringFromClass ([selvklass]), selv, NSStringFromSelector (_cmd)); - (void) viewDidAppear: (BOOL) animert [super viewDidAppear: animated]; NSLog (@ "% @ (% p) -% @", NSStringFromClass ([selvklass]), selv, NSStringFromSelector (_cmd)); - (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation varighet: (NSTimeInterval) varighet [super willRotateToInterfaceOrientation: toInterfaceOrientation varighet: varighet]; NSLog (@ "% @ (% p) -% @", NSStringFromClass ([selvklass]), selv, NSStringFromSelector (_cmd)); - (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation [super didRotateFromInterfaceOrientation: fromInterfaceOrientation]; NSLog (@ "% @ (% p) -% @", NSStringFromClass ([selvklass]), selv, NSStringFromSelector (_cmd)); @slutt
Det er ikke noe spesielt med vår visningskontroller selv, bortsett fra at vi har overstyrt de ulike tilbakeringingene, slik at vi kan logge dem når vår ViewController forekomst blir et barn til vår rotkontroller og et utseende eller rotasjonshendelse forekommer.
I all tidligere kode, _cmd
refererer til väljeren som svarer til metoden som utførelsen er inne i. NSStringFromSelector ()
konverterer den til en streng. Dette er en rask og enkel måte å få navnet på den nåværende metoden uten å måtte skrive det ut manuelt.
La oss kaste en navigasjonsregulator og en fanebryter inn i blandingen. Denne gangen bruker vi Storyboards.
Opprett en ny fil, og under iOS> brukergrensesnitt, velge storyboard. Sett enhetens familie til iPhone, og nev det NavStoryBoard.
Fra objekter bibliotek, dra og slipp a Navigasjonskontroller objekt inn i lerretet. Dra og slipp a bar knapp element inn i venstre side av navigasjonsfeltet i bordvisningskontrollenhet betegnet som "Root View Controller". Dette inneholder tabellvisningen i lerretet. Gi det noe navn. Jeg har kalt det"Venstre". Formålet er å bekrefte koden vi skrev for å få menylinjen til å skjule / avsløre knappen som sin venstre knapp på navigasjonslinjen, og skyve eventuelle allerede til stede knapper til høyre. Til slutt drar du en Vis kontrolleren eksempel og plasser den til høyre for kontrolleren med tittelen "Root View Controller"i lerretet.
Klikk hvor det står "Tabellvisning"i midten av den andre kontrolleren, og i attributter inspektør endre innholdet fra "Dynamisk Prototype" til "Statiske celler".
Endrer celleinnholdstype fra dynamisk til statiskDette vil føre til at tre statiske tabellvisningsceller vises i grensesnittbyggeren. Slett alt annet enn ett av disse tabellvisningscellene, og hold nede Styre, klikk og dra fra den gjenværende cellen til visningsregulatoren helt til høyre og slipp ut. Å velge "trykk"under Utvalgssegment. Alt dette gjør at en segue til høyre vises kontrolleren når du klikker på den ensomme cellen fra tabellvisningen. Hvis du vil, kan du slippe en UILabel på bordcellen for å gi den litt tekst. Din storyboard bør se ut som bildet nedenfor.
NavStoryBoardTil slutt, la oss legge til en fanebladskontroller. Akkurat som du gjorde tidligere, opprett en storyboard filen og ring den TabStoryBoard. Dra og slipp a kontrollpanel på fanen element fra objektbibliotek inn i lerretet. Den kommer forhåndskonfigurert med to faner, og hvis du vil kan du endre bakgrunnsfargen til de to tabbevisningsregulatorene ved å klikke på visningen som svarer til enten å se kontrolleren og endre "bakgrunn"alternativet i Attributtsinspektør. På denne måten kan du bekrefte at visning av kontroller på kontrollpanelet gjennom fanen fungerer som den skal.
Din historiebrett skal se slik ut.Nå er det på tide å sette alt opp i AppDelegate.
Erstatt koden i AppDelegate.m med følgende kode.
#import "AppDelegate.h" #import "RootController.h" #import "ViewController.h" @implementation AppDelegate - (BOOL) søknad: (UIApplication *) søknad didFinishLaunchingWithOptions: (NSDictionary *) launchOptions self.window = [[UIWindow alloc] initWithFrame: grenser for [[UIScreen mainScreen]]]; UIStoryboard * tabStoryBoard = [UIStoryboard storyboardWithName: @ "TabStoryboard" bundle: null); UIStoryboard * navStoryBoard = [UIStoryboard storyboardWithName: @ "NavStoryboard" bunt: null); 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 allokere] initWithViewControllers: @ [tabController, redVC, greenVC, navController] ogMenuTitles: @ [@ "Tab", @ "Red", @ "Green", @ "Nav"]]; self.window.rootViewController = menuController; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; returnere JA;
Alt vi gjorde var å lage forekomster av ViewController
og instantier navigasjons- og kategorien kontrolleren fra de to storyboards. Vi passerte dem i en matrise til vår RootController
s tilfelle. Det er containerkontrolleren vi implementerte i starten. Vi gjorde dette sammen med en rekke strenger for å nevne visningskontrollene i menyen. Nå skal vi bare angi vår initialiserte Root Controller-forekomst som vinduet rootViewController
eiendom.
Bygg og kjør appen. Du har nettopp implementert containerinneslutning! Trykk på de forskjellige tabellcellene i menyen for å erstatte det synlige lysbildet med den nye som skyves inn fra høyre. Legg merke til hvordan, for navigasjonsstyringsinstansen (kalt "NavC"i menyen),"Venstre"-knappen har flyttet ett sted til høyre, og menylinjeknappen har tatt opp venstre stilling. Du kan endre retningen til landskapet og kontrollere at alt ser riktig ut.
Simulator skjermbilderI denne innledende veiledningen så vi på hvordan kontrollenhetskontrollen er implementert i iOS 6. Vi utviklet en enkel versjon av et tilpasset appgrensesnitt som har fått mye popularitet og ofte sett i svært brukte apper som Facebook og Path. Implementeringen vår var så enkel som mulig, så vi kunne dissekere det enkelt og få grunnleggende rett. Det er mange sofistikerte åpenkilde implementeringer av denne typen kontroller som du kan laste ned og studere. Et raskt Google-søk dukker opp JASidePAnels
og SWRevealViewController
, blant andre.
Her er noen ideer for deg å jobbe med.
En ting jeg vil nevne her er at i Xcode 4.5 og videre, er det et nytt grensesnittbyggerobjekt som heter "Containervisning"som kan vise innholdet i en visningskontroller, og dermed brukes til å implementere inneslutning direkte i din storyboard! Glad koding!