Tutorial: PageViewController

Hallo Zusammen,

heute beschäftigen wir uns mit dem PageViewController. Ein PageViewController ist eine Art Container. Er hält mehrere Seite zusammen und ermöglicht es dem Nutzer durch diese zu blättern. Die „Seiten“ sind jedoch vollwertige ViewController mit all ihren Möglichkeiten. Daraus ergeben sich natürlich spannende Kombinationen.

Ich wähle für dieses Tutorial die Kombination aus PageViewController und TableViewController – einfach weil es für diese Zusammenstellung nur wenig Tutorials gibt. Wir erstellen also eine App die drei TableViewController in einem PageViewController bündelt. Der Nutzer kann dann in der „fertigen“ App seitlich durch die Tabellen swipen.

Am Ende des Tutorials werden wir 3 View Controller im Storyboard haben. Einen StartViewController, einen PageViewController und einen TableViewController. Der StartViewController wird beim starten der App aufgerufen und hat die Aufgabe den PageViewController zu erstellen, sowie die anzuzeigenden Daten vorzuhalten. Der PageViewController wiederum initialisiert beliebig viele TableViewController, je nach Bedarf.

Fange wir mit einem neuen Projekt vom Type SingleViewApplication an. Der Eine oder Andere mag nun denken – Moment, es gibt doch ein fertiges PageViewController Template. Richtig – allerdings wäre dann das Tutorial hier zu Ende und außerdem möchten wir ja das Prinzip verstehen.

Single View ApplicationUnser erster Anlaufpunkt ist das Storyboard. Zu dem bereits vorhandenen einsamen ViewController ziehen wir rechts einen „Page View Controller“ und einen „Table View Controller“ auf das Storyboard. Dem PageViewController müssen wir nicht all zuviel Beachtung schenken. Wir vergeben lediglich eine Storyboard ID im Identity Inspector.

PageViewControllerDen Table View Controller wollen wir jedoch etwas verändern. Ich habe oberhalb der Tableview ein Label eingefügt. In diesem Label werden wir eine Überschrift anzeigen, so dass der Nutzer stets weiß in welcher Tabelle er sich gerade befindet. Für den TableViewController vergeben wir die Storyboard ID „PageTableController“.

ID2In dem Screenshot ist bereits eine Klasse „PageTableController“ hinterlegt, die wir noch gar nicht erstellt haben. Das holen wir nun nach. Über New » File erstellen wir eine neue Subklasse von UITableViewController und nennen sie PageTableController.

Wir wollen ja den Inhalt des Labels ändern, je nach dem welche Tabelle angezeigt wird. Daher benötigen wir das Label als IBOutlet. Ein Weg ein IBOutlet zu erstellen ist über den Assistent Editor. Wir vergewissern uns, dass die Datei PageTableController.h im Assistent Editor geöffnet ist. Anschließend ziehen wir das Label mit gedrückter ctrl-Taste in die Header Datei. Ich habe mein IBOutlet headerLabel genannt.

Label IBOutletDie beiden im Screenshot bereits ersichtlichen propertys müssen wir auch noch ergänzen.

@property (nonatomic,assign) NSUInteger pageIndex;
@property (nonatomic,strong) NSString *tableTitle;

Im PageIndex merken wir uns auf welcher Seite wir sind und der tableTitle ist einfach nur die Überschrift, welche wir später im headerLabel anzeigen.  Damit sind unsere Arbeiten am Storyboard auch schon abgeschlossen.

Storyboard

Manch einer wird sich nun sicherlich wundern – alle ViewController schweben ohne Verbindung im Storyboard nebeneinander. Das ist korrekt so, in diesem Beispiel brauchen wir keine Verbindungen.

Widmen wir uns dem StartViewController – ich habe den initial vom Template erstellen ViewController behalten. Er soll mein StartViewController werden. Wir öffnen demnach ViewController.h. Wie oben beschrieben, soll der StartViewController die anzuzeigenden Daten vorzuhalten und die ViewController für den PageViewController liefern.

#import "PageTableController.h"

@interface ViewController : UIViewController <UIPageViewControllerDataSource>

@property (strong, nonatomic) UIPageViewController *pageViewController;
@property (strong, nonatomic) NSArray *titlesArray;

@end

TitlesArray ist unsere Datenquelle für die Titel über den einzelnen Tabellen. Desweiteren möchten wir eine Referenz auf den zu erstellenden PageViewController haben. PageTableController.h müssen wir importieren, da unser StartViewController Instanzen dieser Klasse erstellen soll. Zu guter Letzt geben wir an, dass unsere Klasse konform zum <UIPageViewControllerDataSource> Protokoll ist. Um dem Protokoll gerecht zu werden, müssen wir die beiden folgenden Methoden implementieren.

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
    NSUInteger index = ((PageTableController*) viewController).pageIndex; 
    if ((index == 0) || (index == NSNotFound)) {
        return nil;
    }
    index--;
    return [self getViewControllerForIndex:index];
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
    NSUInteger index = ((PageTableController*) viewController).pageIndex;
    if (index == NSNotFound) {
        return nil;
    }
    index++;
    if (index == [self.titlesArray count]) {
        return nil;
    }
    return [self getViewControllerForIndex:index];
}

Die Klassen laden den vorhergehenden und den nachfolgenden ViewController und geben ihn an die aufrufende Instanz zurück. Zunächst lesen wir das Property pageIndex des aktuellen ViewControllers aus, um zu wissen an welcher Stelle wir stehen. Wir machen anschließend ein paar Checks, um zu testen ob wir uns im Rahmen der gültigen Seiten befinden. Sollte der Nutzer auf der äußersten rechten Seite sein und nach rechts blättern, fangen wir diesen Fall z.B. mit „if (index == [self.titlesArray count])“ ab und liefern Nil zurück. Die Funktion getViewControllerForIndex wird als Fehler angezeigt, da sie noch fehlt. Das sollten wir ändern.

- (PageTableController *)getViewControllerForIndex:(NSUInteger)index
{
    if (([self.titlesArray count] == 0) || (index >= [self.titlesArray count])) {
        return nil;
    }

    PageTableController *aPageTableController = [self.storyboard instantiateViewControllerWithIdentifier:@"PageTableController"];
    aPageTableController.tableTitle = self.titlesArray[index];
    aPageTableController.pageIndex = index;

    return aPageTableController;
}

In dieser Methode erstellen wir einen PageTableController über instantiateViewControllerWithIdentifier. Den Identifier haben wir ganz am Anfang im Storyboard unter Storyboard ID hinterlegt – es wird also eine Instanz des dort zusammengestellten ViewControllers instanziiert. Anschließend füllen wir noch die Properties des neu erstellen ViewControllers.

Im UIPageViewControllerDataSource Protokoll gibt es noch zwei optionale Methoden, die wir jetzt ebenfalls implementieren.

- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController
{
    return [self.titlesArray count];
}

- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
{
    return 0;
}

Mit der ersten Methode definieren die maximale Anzahl an Seiten und mit der zweiten Methode geben wir die StartSeite an.

Ganz am Anfang haben wir festgelegt, dass der StartViewController den PageViewController erstellen soll. Der Teil fehlt noch. Außerdem ist unser titlesArray noch leer, so dass wir zu Zeit nicht eine einzige Seite erstellen würden. Gehen wir zu viewDidLoad im ViewController.m

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.titlesArray = [NSArray arrayWithObjects:@"erste Seite", @"zweite Seite", @"was tolles", nil];

    // pageViewController erstellen
    self.pageViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"PageViewController"];
    self.pageViewController.dataSource = self;

    PageTableController *firstViewController = [self getViewControllerForIndex:0];
    NSArray *arrayOfViewControllers = @[firstViewController];
    [self.pageViewController setViewControllers:arrayOfViewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];

    [self addChildViewController:self.pageViewController];
    [self.view addSubview:self.pageViewController.view];
    [self.pageViewController didMoveToParentViewController:self];
}

Wir erstellen uns ein Array mit Seitentiteln. Anschließend wird der pageViewController nun endlich an Hand der Storyboard ID erstellt. Der pageViewController bekommt nun auch gesagt, dass er seine Daten von self (also ViewController.m) erhält. Zum Schluß präsentieren wir den PageViewController, so dass der Nutzer den StartViewController eigentlich nie zu Gesicht bekommt.

Jetzt können wir das erste mal auf Start drücken und erleben bereits drei Tabellen durch die wir swipen können. Allerdings ändert sich der Titel der Tabelle noch nicht. Das ist auch logisch, denn wir setzten zwar das Property  aPageTableController.tableTitle, allerdings aktualisieren wir nie unser Label. Daher ergänzen wir viewDidLoad vom PageTableController.

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.headerLabel.text = self.tableTitle;
}

Toll!

erste Seite

Ich gebe zu, der Page Indikator fehlt bei euch noch. Dafür muss zunächst der Transition Style des PageViewControllers im Storyboard von Page Curl auf Scroll umgestellt werden.

page curl

Anschließend ergänzen wir in AppDelegate.m innerhalb von applicationDidFinish…

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{   
    UIPageControl *pageControl = [UIPageControl appearance];
    pageControl.pageIndicatorTintColor = [UIColor lightGrayColor];
    pageControl.currentPageIndicatorTintColor = [UIColor blackColor];
    pageControl.backgroundColor = [UIColor whiteColor];

    return YES;
}

Die Tabellen sind zwar noch leer, aber das soll für heute dennoch reichen. Wer selber weiter forschen möchte um die Tabellen zu füllen, hier ein paar Tipps:

3 thoughts on “Tutorial: PageViewController

  • Eine Erweiterung dieses Tutorials um die Erstellung von Tabelleninhalten und weiter führenden Detailseiten wäre echt super!!!

    Ansonsten gut und verständlich erklärt.

  • Hallo,

    bei mir funktioniert im TableView Container
    – (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSLog(@“Click…“);
    }
    nicht.

    Was mache ich falsch ?

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.