In deze zelfstudie zullen we een minimalistische versie van de gebruikersinterface voor Facebook / Path-stijl implementeren. Het doel zal zijn om inzicht te krijgen in het gebruik van view controller-insluiting om aangepaste stroom in uw app te implementeren.
View controllers vormen een essentieel onderdeel van elke iOS-applicatie, hoe klein, groot, eenvoudig of complex ook. Ze bieden de "lijm-logica" tussen het gegevensmodel van uw app en de gebruikersinterface.
Over het algemeen zijn er twee soorten view controllers:
Een containercontroller kan een zichtbare component van zichzelf hebben, maar functioneert in principe als host voor contentview-controllers. Containercontrollers dienen voor het 'verkeer' van het komen en gaan van contentview-controllers.
UINavigationController
, UITabBarController
en UIPageViewController
zijn voorbeelden van containerview-controllers die worden geleverd met de iOS SDK. Overweeg hoe de drie verschillen in termen van de toepassingsstromen die ze veroorzaken. De navigatiecontroller is geweldig voor een app met een dieptepunttype, waarbij de selectie die de gebruiker in één scherm maakt van invloed is op de keuzes die hij in het volgende scherm presenteert. De tabbalkcontroller is geweldig voor apps met onafhankelijke functies, waardoor u gemakkelijk kunt wisselen door eenvoudig op een tabtoets te drukken. Ten slotte presenteert de besturingseenheid voor paginaweergave een boekmetafoor, waardoor de gebruiker heen en weer kan bladeren tussen pagina's met inhoud.
Het belangrijkste om in gedachten te houden is dat een daadwerkelijke screening van inhoud die wordt gepresenteerd via een van deze containerzichtcontrollers zelf moet worden beheerd, zowel in termen van de gegevens waarvan het is afgeleid (het model) als de presentatie op het scherm (de weergave), wat opnieuw de taak van een view controller zou zijn. Nu hebben we het over content view controllers. In sommige apps, met name op de iPad, omdat op het grotere scherm meer inhoud tegelijk kan worden weergegeven, moeten mogelijk zelfs verschillende weergaven op het scherm afzonderlijk worden beheerd. Dit vereist meerdere view controllers op het scherm in een keer. Dit alles houdt in dat de view-controllers in een goed ontworpen app op een hiërarchische manier moeten worden geïmplementeerd, waarbij zowel container- als contentview-controllers hun respectieve rollen spelen.
Vóór iOS 5 waren er geen manieren om een hiërarchische (dat wil zeggen, ouder-kind) relatie tussen twee view-controllers te declareren en daarom geen 'juiste' manier om een aangepaste applicatiestroom te implementeren. Een van de twee moest het doen met de ingebouwde typen, of op een lukrake manier, die in feite bestond uit het vasthouden van weergaven die werden beheerd door een view-controller in de weergavehiërarchie van de weergave die werd beheerd door een andere view-controller. Dit zou inconsistenties creëren. Een weergave zou bijvoorbeeld in de weergavehiërarchie van twee controllers terechtkomen zonder dat een van deze controllers de andere herkent, wat soms tot vreemd gedrag leidt. Containment werd geïntroduceerd in iOS 5 en enigszins verfijnd in iOS 6, en het biedt de mogelijkheid om ouder- en kindzichtcontrollers in een hiërarchie te formuleren. In feite vereist een correcte weergavebesturingselementconfiguratie dat als weergave B een subweergave (kind) van weergave A is en als deze niet onder het beheer van dezelfde weergavecontroller staan, B's weergavecontroller dan moet worden gemaakt als A's view controller's child.
U kunt zich afvragen of er sprake is van een concreet voordeel geboden door view controller containment naast het voordeel van het hiërarchische ontwerp dat we hebben besproken. Het antwoord is ja. Houd er rekening mee dat wanneer een weergaveregelaar op het scherm wordt weergegeven of verdwijnt, we mogelijk bronnen moeten instellen of verwijderen, informatie opruimen, ophalen of opslaan van / naar het bestandssysteem. We kennen allemaal uiterlijk callbacks. Door de ouder-kindrelatie expliciet te verklaren, zorgen we ervoor dat de bovenliggende controller callbacks doorstuurt naar zijn kinderen wanneer iemand op het scherm verschijnt of van het scherm gaat. Rotatie-callbacks moeten ook worden doorgestuurd. Wanneer de oriëntatie verandert, moeten alle view controllers op het scherm dit weten zodat ze hun inhoud op de juiste manier kunnen aanpassen.
Wat houdt dit allemaal in, in termen van code? View-controllers hebben een NSArray
eigenschap genoemd childViewControllers
en onze verantwoordelijkheden omvatten het toevoegen en verwijderen van child view controllers van en naar deze array in de ouder door geschikte methoden aan te roepen. Deze methoden omvatten addChildViewController
(een beroep gedaan op de ouder) en removeFromParentViewController
(riep het kind op) wanneer we de ouder-kindrelatie willen maken of breken. Er zijn ook een paar notificatieberichten verzonden naar de child view-controller aan het begin en aan het einde van het toevoegings- / verwijderingsproces. Dit zijn willMoveToParentViewController:
en didMoveToParentViewController:
, verzonden met de juiste bovenliggende controller als argument. Het argument is nul
, als het kind wordt verwijderd. Zoals we zullen zien, zal een van deze berichten automatisch voor ons worden verzonden, terwijl de andere onze verantwoordelijkheid is om te verzenden. Dit hangt ervan af of we het kind toevoegen of verwijderen. We zullen de exacte volgorde kort bestuderen wanneer we dingen in code implementeren. De child-controller kan op deze meldingen reageren door de bijbehorende methoden te implementeren als deze iets moet doen ter voorbereiding van deze evenementen.
We moeten ook de aan de child view-controller gekoppelde views toevoegen / verwijderen aan de hiërarchie van de parent, met behulp van methoden zoals addSubview:
of removeFromSuperview
), inclusief het uitvoeren van eventuele begeleidende animaties. Er is een gemaksmethode (-) transitionFromViewController: toViewController: duur: options: animaties: voltooiing:
waarmee we het proces van het wisselen van besturingselementen voor kinderopvragingen op het scherm met animaties kunnen stroomlijnen. We zullen de exacte details bekijken wanneer we de code schrijven - wat de volgende is!
Maak een nieuwe iOS-app in Xcode op basis van de "Lege applicatie"template. Maak er een iOS-app van met ARC ingeschakeld VCContainmentTut.
Een nieuw project makenMaak een nieuwe klasse genaamd RootController
. Maak het een UIViewController
subklasse. Zorg ervoor dat selectievakjes niet zijn geselecteerd. Dit wordt onze subklasse voor containerzichtcontrollers.
Vervang de code in RootViewController.h met het volgende.
#importeren@interface RootController: UIViewController // (1) - (id) initWithViewControllers: (NSArray *) viewControllers andMenuTitles: (NSArray *) titels; // (2) @end
Onze containercontroller heeft een tabelweergave die functioneert als ons menu. Als u op een cel tikt, wordt de momenteel zichtbare view-controller vervangen door degene die is geselecteerd via de tik van de gebruiker..
Verwijzend naar de punten in de code,
Laten we een kijkje nemen om te zien hoe ons eindproduct eruit zal zien, zodat u een mentaal beeld hebt om de implementatie aan te koppelen.
Het helpt ons te beseffen dat onze containerzichtcontroller vrij veel lijkt op een tabbladweergavecontroller. Elk item in het menu komt overeen met een onafhankelijke view controller. Het verschil tussen onze "glijdend menu"controller en de tab-controller zijn grotendeels visueel."glijdend menu"is een beetje een verkeerde benaming omdat het eigenlijk de contentview-controller is die verschuift om het onderliggende menu te verbergen of te onthullen.
Ga verder met de implementatie en vervang alle code in RootController.m met de volgende code.
#define kExposedWidth 200.0 #define kMenuCellID @ "MenuCell" #import "RootController.h" @interface RootController () @eigenschap (niet-atomisch, sterk) UITableView * -menu; @property (nonatomic, strong) NSArray * viewControllers; @property (nonatomic, strong) NSArray * menuTitels; @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, @ "Er moet één en slechts één menutitel zijn die overeenkomt met elke view-controller!"); // (1) NSMutableArray * tempVCs = [NSMutableArray arrayWithCapacity: viewControllers.count]; self.menuTitles = [menuTitels kopiëren]; for (UIViewController * vc in viewControllers) // (2) if (! [vc isMemberOfClass: [UINavigationController class]]) [tempVCs addObject: [[UINavigationController alloc] initWithRootViewController: vc]]; else [tempVCs addObject: vc]; UIBarButtonItem * revealMenuBarButtonItem = [[UIBarButtonItem alloc] initWithTitle: @ "Menu" -stijl: UIBarButtonItemStylePlain-doel: zelfactie: @selector (toggleMenuVisibility :)]; // (3) UIViewController * topVC = ((UINavigationController *) tempVCs.lastObject) .topViewController; topVC.navigationItem.leftBarButtonItems = [@ [revealMenuBarButtonItem] arrayByAddingObjectsFromArray: topVC.navigationItem.leftBarButtonItems]; self.viewControllers = [tempVCs kopiëren]; self.menu = [[UITableView alloc] init]; // (4) self.menu.delegate = self; self.menu.dataSource = self; terugkeer zelf; - (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 = YES; [self adjustContentFrameAccordingToMenuVisibility]; // (7) [self.viewControllers [0] didMoveToParentViewController: self]; // (8) - (void) toggleMenuVisibility: (id) afzender // (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 animations: ^ visibleViewController.view.frame = CGRectMake (kExposedWidth, 0, size.width, size.height); ]; else [UIView animateWithDuration: 0.5 animations: ^ visibleViewController.view.frame = CGRectMake (0, 0, size.width, size.height); ]; - (void) replaceVisibleViewControllerWithViewControllerAtIndex: (NSInteger) index // (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 addChildViewController: incomingViewController]; // (13) [[UIApplication sharedApplication] beginIgnoringInteractionEvents]; // (14) [self transitionFromViewController: outgoingViewController // (15) toViewController: incomingViewController duration: 0.5 options: 0 animations: ^ outgoingViewController.view.frame = [self offScreenFrame]; completion: ^ (BOOL finished) [UIView animateWithDuration: 0.5 animations: ^ [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) sectie return self.menuTitles.count; - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: kMenuCellID]; cell.textLabel.text = self.menuTitles [indexPath.row]; terugkeer cel; - (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); @end
Nu voor een uitleg van de code. De onderdelen die ik heb benadrukt vanwege de nadruk, zijn met name relevant voor de insluiting-implementatie.
UIViewController
en NSString
typen respectievelijk. Je zou kunnen overwegen om het te doen. Merk op dat we reeksen onderhouden voor elk van deze, genaamd viewcontrollers
, en menuTitles
.viewDidLoad
, na het configureren en toevoegen van de menutabelweergave aan de weergave van onze root-controller, brengen we in onze app de eerste view-controller in de viewcontrollers
rangschikking. Door de addChildViewController:
bericht aan onze root-controller voeren we onze eerste inperkingsgerelateerde verantwoordelijkheid uit. U moet weten dat dit de melding veroorzaakt willMoveToParentViewController:
worden opgeroepen op de kindcontroller.zelf
, de RootController-instantie, als het argument. In onze kindcontroller kunnen we deze methode implementeren als dat nodig is.adjustContentFrameAccordingToMenuVisibility
laat ons het frame van de contentview-controller aanpassen om ons te laten weten of het menu verborgen is of niet. Zo ja, dan overlapt het de superview. Anders wordt deze doorgeschoven naar rechts kExposedWidth
. Ik heb dat ingesteld op 200 punten.replaceVisibleViewControllerWithViewControllerAtIndex
stelt ons in staat om view controllers en de corresponderende views uit de hiërarchie uit te wisselen. Om onze animatie, die bestaat uit het opzij schuiven van de vervangen beeldcontroller naar rechts en het vervolgens van de plaats halen van de vervangende controller, af te beelden, definiëren we enkele rechthoekige frames..nul
. Zodra we deze stap hebben voltooid, stopt deze weergave-controller met het ophalen van weergave- en rotatie-callbacks van de ouder. Dit is logisch omdat het niet langer een actief onderdeel van de app is.didMoveToParentViewController
bericht met zelf als het argument.removeFromParentViewController
bericht. Je zou dat moeten weten didMoveToParentViewController:
met nul
als een argument voor je wordt verzonden.-tableView: didSelectRowAtIndexPath:
methode.Mogelijk hebt u de reeks oproepen met betrekking tot de beheersing van de controller een beetje verwarrend gevonden. Het helpt om samen te vatten.
addChildViewController:
op de ouder met het kind als argument. Dit veroorzaakt het bericht willMoveToParentViewController:
naar het kind gestuurd worden met de ouder als argument.didMoveToParentViewController:
op het kind met de ouder als argument.willMoveToParentViewController:
op het kind met nul
als het argument.removeFromParentViewController
voor het kind. De oorzaak van het bericht didMoveToParentViewController
met nul
als het argument dat namens u aan het kind moet worden gestuurd.Laten we de verschillende typen view controllers testen die aan onze root-controller zijn toegevoegd! Maak een nieuwe subklasse van UIViewController
riep ViewController
, alle opties uitgeschakeld houden.
Vervang de code in ViewController.m met de volgende code.
#import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void) willMoveToParentViewController: (UIViewController *) parent NSLog (@ "% @ (% p) -% @", NSStringFromClass ([self class] ), self, NSStringFromSelector (_cmd)); - (void) didMoveToParentViewController: (UIViewController *) parent NSLog (@ "% @ (% p) -% @", NSStringFromClass ([self class]), self, NSStringFromSelector (_cmd)); - (void) viewWillAppear: (BOOL) geanimeerde [super viewWillAppear: geanimeerd]; NSLog (@ "% @ (% p) -% @", NSStringFromClass ([zelfklasse]), self, NSStringFromSelector (_cmd)); - (void) viewDidAppear: (BOOL) geanimeerde [super viewDidAppear: geanimeerd]; NSLog (@ "% @ (% p) -% @", NSStringFromClass ([zelfklasse]), self, NSStringFromSelector (_cmd)); - (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation duration: (NSTimeInterval) duration [super willRotateToInterfaceOrientation: toInterfaceOrientation duration: duration]; NSLog (@ "% @ (% p) -% @", NSStringFromClass ([zelfklasse]), self, NSStringFromSelector (_cmd)); - (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation [super didRotateFromInterfaceOrientation: fromInterfaceOrientation]; NSLog (@ "% @ (% p) -% @", NSStringFromClass ([zelfklasse]), self, NSStringFromSelector (_cmd)); @end
Er is niets speciaals aan onze view controller zelf, behalve dat we de verschillende callbacks hebben overschreven, zodat we ze kunnen loggen wanneer onze ViewController instance wordt een kind voor onze root-controller en er treedt een verschijning of rotatie op.
In alle voorgaande code, _cmd
verwijst naar de selector die overeenkomt met de methode waarin onze uitvoering zich bevindt. NSStringFromSelector ()
converteert het naar een string. Dit is een snelle en gemakkelijke manier om de naam van de huidige methode te krijgen zonder deze handmatig te hoeven typen.
Laten we een navigatiecontroller en een tabcontroller in de mix gooien. Deze keer gebruiken we Storyboards.
Maak een nieuw bestand, en onder iOS> Gebruikersinterface, Kiezen storyboard. Stel de apparaatfamilie in iPhone, en noem het NavStoryBoard.
Van de objecten bibliotheek, slepen en neerzetten a Navigatiecontroller object in het canvas. Versleep a streepjesknopitem links in de navigatiebalk in de tafelweergave controller aangeduid als "Root View Controller". Dit bevat de tabelweergave op het canvas. Geef het een naam. Ik heb het genoemd"Links"Het doel is om de code die we hebben geschreven te verifiëren om de knop verbergen / zichtbaar maken van de menubalk als de meest linkse knop op de navigatiebalk te vervangen door de reeds aanwezige knoppen naar rechts te duwen. Bekijk Controller voorbeeld en plaats deze rechts van de controller met de titel 'Root View Controller"op het canvas.
Klik waar het staat "Tabelweergave"in het midden van de tweede controller en in de attributen inspector verander de inhoud van "Dynamisch prototype" naar "Statische cellen".
Het celinhoudstype wijzigen van dynamisch in statischHierdoor verschijnen er drie statische cellen in de tabelweergave in de interfacebuilder. Verwijder alle cellen op één na, maar houd ze ingedrukt Controle, klik en sleep van de overgebleven cel naar de view-controller uiterst rechts en laat los. Selecteer "Duwen" onder Selectie Segue. Dit alles zorgt ervoor dat er een segue naar de rechterview-controller gaat als u in de tabelweergave op de eenzame cel tikt. Als je wilt, kun je een a laten vallen UILabel op de tafelcel om het wat tekst te geven. Je storyboard moet er ongeveer zo uitzien als de onderstaande foto.
NavStoryBoardLaten we tot slot een tabbalkcontroller toevoegen. Net zoals je eerder hebt gedaan, maak een storyboard bestand en bel het TabStoryBoard. Versleep a tabbalkcontroller item uit de objectbibliotheek in het canvas. Het wordt voorgeconfigureerd met twee tabbladen en als je wilt, kun je de achtergrondkleur van de twee weergaveregelaars met tabbladen wijzigen door te klikken op de weergave die overeenkomt met de view controller en de "achtergrond"optie in de Kenmerken Inspector. Op deze manier kunt u controleren of de selectie van de weergaveregelaar via het tabblad correct werkt.
Je storyboard zou er zo uit moeten zien.Nu is het tijd om alles in te stellen in de AppDelegate.
Vervang de code in AppDelegate.m door de volgende code.
#import "AppDelegate.h" #import "RootController.h" #import "ViewController.h" @implementation AppDelegate - (BOOL) -toepassing: (UIApplication *) -toepassing didFinishLaunchingWithOptions: (NSDictionary *) launchOptions self.window = [[UIWindow toewijzen] initWithFrame: [[UIScreen mainScreen] bounds]]; UIStoryboard * tabStoryBoard = [UIStoryboard storyboardWithName: @ "TabStoryboard" -bundel: nihil]; UIStoryboard * navStoryBoard = [UIStoryboard storyboardWithName: @ "NavStoryboard" -bundel: nihil]; 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] enMenuTitles: @ [@ "Tab", @ "Red", @ "Green", @ "Nav"]]; self.window.rootViewController = menuController; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; terugkeer JA;
Alles wat we deden was het creëren van instanties van ViewController
en maak een begin met de navigatie- en tabcontroller van de twee storyboards. We hebben ze in een array doorgegeven aan ons RootController
's exemplaar. Dat is de containercontroller die we bij de start hebben geïmplementeerd. We deden dit samen met een array van strings om de view controllers in het menu te noemen. Nu zullen we onze geïnitialiseerde Root Controller-instantie gewoon als venster aanduiden rootViewController
eigendom.
Bouw en voer de app uit. U hebt zojuist Container containment geïmplementeerd! Tik op de verschillende tabelcellen in het menu om de zichtbare dia te vervangen door de nieuwe dia van rechts. Merk op hoe, voor de navigatiecontroller-instantie (genaamd "NavC"in het menu), de"Linksknop is een plaats naar rechts verschoven en de menubalkknop heeft de meest linkse positie ingenomen. U kunt de richting wijzigen in liggend en controleren of alles er goed uitziet.
Simulator schermafbeeldingenIn deze inleidende zelfstudie hebben we gekeken naar hoe view controller-insluiting is geïmplementeerd in iOS 6. We hebben een eenvoudige versie van een aangepaste app-interface ontwikkeld die veel populariteit heeft gekregen en vaak wordt gezien in veelgebruikte apps zoals Facebook en Path. Onze implementatie was zo eenvoudig mogelijk, dus we konden het gemakkelijk ontleden en de basis goed krijgen. Er zijn veel geavanceerde open-sourceimplementaties van dit type controller die u kunt downloaden en bestuderen. Er verschijnt een snelle Google-zoekopdracht JASidePAnels
en SWRevealViewController
, onder andere.
Hier zijn enkele ideeën om aan te werken.
Een ding dat ik hier wil vermelden is dat er vanaf Xcode 4.5 een nieuw interface builder-object is genaamd "Containerweergave"die de inhoud van een view-controller kan weergeven en dus kan worden gebruikt om containment direct in je storyboard te implementeren! Happy coding!