Cos’è UIPageViewController? Come da nome, UIPageViewController è un controller nativo di iOS (introdotto con la release 5.0) che permette di gestire le nostre viste in una struttura “a pagine”. Per fare un’esempio pratico è quel controllo che viene normalmente utilizzato per visualizzare le pagine in iBooks e che ci fornisce out-of-the-box il bell’effetto “pagina piegata” (page curl) mentre sfogliamo.
Stranamente la documentazione Apple, in merito, è un po’ frammentaria e, a dirla tutta, non esiste una vera e propria guida ufficiale su come utilizzare questo controllo, ne tantomeno di come farlo usando lo Storyboard.
Ovviamente, l’uso tramite Storyboard è assolutamente opzionale, e potremmo utilizzare i classici nib/xib; tuttavia lo Storyboard funziona in maniera un pochino più intelligente, ci permette infatti di avere l’intera interfaccia della nostra applicazione in un’unico file, nel quale si va anche a descrivere il flusso delle applicazioni.
Cosa faremo
Con questo tutorial andremo a realizzare una semplice applicazione che ci permetterà di sfogliare avanti ed indietro due differenti viste.
Come vedrete le viste sono decisamente semplici, e la struttura del progetto anche, ma credo sia un buon punto di partenza per capire come funziona questo controllo.
Iniziamo con il progetto
Per mostrare bene il funzionamento creeremo un nuovo progetto che popoleremo con tutto il necessario.
Per iniziare, quindi, lanciamo il nostro fidato Xcode e creiamo una nuova “Empty Application”.
Chiamiamola “Pager” ed assicuriamoci di impostare come device iPhone e di utilizzare ARC:

Premiamo “Next”, quindi “Create” ed eccoci: abbiamo un bel progetto che, fondamentalmente, non fa nulla.
Per prima cosa, andiamo a rimuovere le parti che non ci interessano dal progetto e aggiungiamo lo Storyboard, istruendo il progetto per utilizzarlo.
Creiamo quindi il file Storyboard premendo su File -> New -> File (oppure cmd+N) e selezionando “Storyboard” dalla sezione “User Interface”.
Selezioniamo quindi iPhone come “Device Family” e chiamiamolo “Storyboard”.
A questo punto, i passaggi per dire al progetto di utilizzarlo sono due: per prima cosa dobbiamo agganciare il nostro Storyboard al progetto stesso; per fare questo premiamo sul progetto e, nella sezione “Main Storyboard” scegliamo il nostro file dalla lista:

Dopodichè, dobbiamo rimuovere dal metodo application:didFinishLaunchingWithOptions: tutte le parti relative al caricamento della UIWindow di default.
Apriamo quindi il file AppDelegate.m e rimpiazziamo il suddetto metodo con il seguente:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
return YES;
}
A questo punto siamo pronti per creare la nostra interfaccia.
Definiamo l’interfaccia
Andiamo a definire la nostra interfaccia grafica; aprendo lo Storyboard ci troveremo davanti ad un file vuoto. Aggiungiamo il nostro bel UIPageViewController (lo trovate negli Objects, indicato dalla classica sfera gialla (che indica i controllers) e da un’icona a forma di libro. Prendiamo e trasciniamo:

Come vedete Xcode aggiunge una freccia che punta al nostro controller appena aggiunto. Questo indica che, all’avvio dell’applicazione, questo sarà la prima vista mostrata. Nel caso in cui non dovesse apparire, comunque, potete tranquillamente selezionare l’UIPageViewController e dall'”Attribute Inspector” selezionare “Is Initial View Controller”.

Adesso aggiungiamo due semplici UIViewController, che altro non sono che le varie “pagine” visualizzate da UIPageViewController.
Io ho configurato uno sfondo colorato ed una UILabel con del testo. Questo è il risultato finale:

Ok, tutto semplice, no? Perfetto, adesso buttiamoci un po’ sul codice.
Definiamo i file necessari
Creeremo ora i file relativi ad ogni singolo view controller che abbiamo specificato nello Storyboard.
Selezioniamo quindi File -> New -> File (o premiamo cmd+N) e, dalla sezione “Cocoa Touch” selezioniamo “Objective-C class”.
Il primo controller sarà una sottoclasse di UIPageViewController e lo chiameremo “RootViewController”; sarà per iPhone o non ci interesserà avere un file di interfaccia xib. Inseriamo i dati e premiamo Next:

Verranno creati due file: RootViewController.h e RootViewController.m
Seguendo la stessa procedura, andiamo a creare due sottoclassi di UIViewController chiamate “FirstViewController” e “SecondViewController”. Alla fine avremo sei file in più nel nostro progetto:

Ora eliminiamo un po’ di codice inutile: selezioniamo, uno per uno, i tre file di implementazione (i file .m per intenderci) e rimuoviamo da essi il metodo initWithNibName:bundle:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
Questo lo facciamo perché utilizziamo lo Storyboard e, questo metodo, viene utilizzato solo in fase di caricamento da nib/xib. A noi quindi non serve e preferiamo avere del codice pulito, giusto??
Ora che abbiamo i file base, dobbiamo associarli ai contenuti dello Storyboard.
Uniamo codice e Storyboard
Cosa occorre fare ora? Dobbiamo dire ai controller presenti nello Storyboard quali classi hanno associate.
Selezioniamo quindi il nostro UIPageViewController e, nel “Identity Inspector” alla voce “Class” inseriamo il nome della nostra sottoclasse di UIPageViewController, quindi RootViewController:

Selezioniamo quindi le due viste e facciamo la stessa cosa, selezionando però come “Class” prima “FirstViewController” e poi “SecondViewController”.

Infine, dobbiamo applicare ad ognuna delle due viste un identificativo che ci permetta, dal codice, di fare riferimento ad esse.
Selezioniamo “Attribute Inspector” ed impostiamo alla voce “Identifier” il nostro identificativo; personalmente ho utilizzato “First View” per la prima pagina e “Second View” per la seconda:

Salviamo lo Storyboard, e torniamo sul codice, dove avverrà la magia.
Mettiamo insieme il tutto
Perfetto, abbiamo lo Storyboard terminato e correttamente associato alle classi che abbiamo creato. Ora dobbiamo dire al nostro UIPageViewController di utilizzare le nostre due viste come pagine.
Andiamo quindi ad aggiungere due proprietà private al nostro RootViewController, che vadano ad identificare le nostre viste. All’inizio del file includiamo i relativi header:
#import "FirstViewController.h"
#import "SecondViewController.h"
Dopodiché nell’estensione privata della classe RootViewController (la trovate in cima al file RootViewController.m), andiamo ad aggiungere le due property; avremo questa estensione:
@interface RootViewController ()
@property (nonatomic, retain) FirstViewController *firstViewController;
@property (nonatomic, retain) SecondViewController *secondViewController;
@end
Ora, quando creiamo una property in realtà stiamo facendo un sacco di cose; nel nostro caso quello che abbiamo fatto è stato:
- Creare due variabili private chiamate _firstViewController e _secondViewController (notare
- il carattere underscore prima del nome)
- Definire i getter per ottenere il contenuto di queste due variabili, firstViewController e secondViewController: (Anche qui, possiamo usare la notazione puntata … = self.firstViewController)
Definire i setter per impostare il contenuto di queste due variabili, setFirstViewController: e setSecondViewController: (Xcode ci permette di far riferimento ad essi anche con la notazione puntata self.firstViewController = …)
Tutto questo viene fatto gratuitamente da Xcode e ci permette di evitare un sacco di codice.
NOTA BENE: Se state utilizzando l’ultima versione di Xcode, non sarà più necessario sintetizzare le proprietà, anche questo verrà fatto automaticamente dall’IDE. Nel caso non siate tra chi usa le ultime versioni, allora dovrete sintetizzare esplicitamente le due proprietà all’inizio dell’implementazione del RootViewController, aggiungendo quindi:
@synthesize firstViewController = _firstViewController;
@synthesize secondViewController = _secondViewController;
Subito dopo la voce @implementation nello stesso file.
Come dicevo prima, le proprietà ci definiscono automaticamente getter e setter per quelle variabili, ma niente ci vieta di fare l’overriding di questo metodi e di personalizzarli.
Nel caso specifico, quello che vogliamo ottenere è che, quando richiediamo per la prima volta uno di questi View Controller, questo venga istanzianto dall’interfaccia definita nello Storyboard.
Andiamo quindi a fare l’override del getter per il firstViewController:
- (FirstViewController *)firstViewController {
if (!_firstViewController) {
UIStoryboard *storyboard = self.storyboard;
_firstViewController = [storyboard instantiateViewControllerWithIdentifier:@"First View"];
}
return _firstViewController;
}
Vediamo cosa stiamo facendo riga per riga:
- Se la variabile _firstViewController è vuota
- Otteniamo lo Storyboard dal controller attuale (ricordate, il nostro RootViewController è nello Storyboard, quindi ha un riferimento ad esso)
- Utilizziamo il metodo della classe UIStoryboard chiamato instantiateViewControllerWithIdentifier: per istanziare un nuovo view controller, ed esattamente quello che ha come “Identifier” la stringa “First View”
- Restituiamo la variable _firstViewController
Da notare che l’utilizzo del condizionale IF ci permette di istanziare il nostro view controller solo la prima volta, velocizzando (per quanto nel nostro semplice progetto se ne potrebbe anche fare a meno) l’esecuzione del programma.
Facciamo la stessa identica cosa con il getter di secondViewController:
- (SecondViewController *)secondViewController {
if (!_secondViewController) {
UIStoryboard *storyboard = self.storyboard;
_secondViewController = [storyboard instantiateViewControllerWithIdentifier:@"Second View"];
}
return _secondViewController;
}
Perfetto, possiamo ora iniziare a costruire il nostro UIPageViewController.
Come funziona UIPageViewController
Questo controller è molto semplice, quello che fondamentalmente ci interessa (e comunque interessa nella maggior parte dei casi) è definire:
- La/le viste presenti inizialmente: UIPageViewController permette di avere N viste all’inizio; in questo modo, se lo usassimo per esempio su iPad, potremmo definire le due pagine dell’ipotetico libro come due viste separate, e visualizzarle contemporaneamente. Nel nostro caso specifico non ci interessa, avremo una singola vista
- Il DataSource: questo è quello che fa funzionare UIPageViewController. In pratica consiste nell’includere due metodi che ci permettono di specificare la vista successiva e quella precedente rispetto ad una determinata vista.
- Il Delegate: non lo useremo, è il metodo che ci permette di eseguire del codice mentre si sfogliano le pagine. Può essere utile nel momento in cui vogliamo utilizzare sempre la stessa istanza di ViewController per ogni pagina, variandone però il contenuto.
Non discuterò delle opzioni quali impostare la direzione di scorrimento (orizzontale o verticale) o la direzione con cui sfogliare le pagine (da destra a sinistra, o viceversa) per un semplice motivo: ehi, stiamo usando lo Storyboard, possiamo settare tutte queste opzioni comodamente dall’Interface Builder.
Implementiamo
Perfetto, ora che abbiamo capito come funziona dobbiamo definire la vista iniziale del nostro UIPageViewController, che sarà la prima pagina. Apriamo quindi il codice di RootViewController.m ed inseriamo questa inizializzazione nel metodo viewDidLoad.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
// Aggancio il view controller iniziale.
[self setViewControllers:@[self.firstViewController]
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:nil];
}
Abbiamo usato il metodo setViewControllers:direction:animated:completion: Questo metodo ha bisogno di diversi parametri, nell’ordine:
- Un’array contenente i view controllerà che saranno usati nella vista iniziale. Come potete vedere ho utilizzato la nuova notazione @[self.firstViewController] semplificata. Questo è l’equivalente di scrivere [NSArray arrayWithObject:self.firstViewController] oppure [NSArray arrayWithObjects:self.firstViewController, nil]
- Una costante che indica la direzione di navigazione. UIPageViewControllerNavigationDirectionForward ci indica che andiamo in avanti
- L’indicazione se vogliamo o meno una transazione animata tra le pagine
- Un’eventuale block di codice da eseguire dopo aver aggiunti i viewControllers (utile se vogliamo inizializzare i contenuti DOPO aver aggiunto i view controllers ad UIPageViewController).
Che fare ora? Dobbiamo agganciare il Data Source (utilizzeremo la stessa classe) e specificare le logiche di funzionamento del UIPageViewController.
Per prima cosa, quindi, apriamo l’interfaccia della classe RootViewController (RootViewController.h) e rendiamola conforme al protocollo UIPageViewControllerDataSource:
@interface RootViewController : UIPageViewController
Ora, sempre nel metodo viewDidLoad, agganciamo il data source al UIPageViewController aggiungendo, subito dopo la chiamata al parent (super). Il metodo diventa così:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
// Aggancio il data source.
self.dataSource = self;
// Aggancio il view controller iniziale.
[self setViewControllers:@[self.firstViewController]
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:nil];
}
Ora dobbiamo implementare i due metodi richiesti dal protocollo.
Il primo di questi due è pageViewController:viewControllerAfterViewController: e ritorna, se non fosse il nome del metodo come scioglilingua, un UIViewController.
In pratica, a parte il riferimento al UIPageViewController ci viene passato come argomento l’UIViewController attualmente visualizzato e ci viene richiesto quale sarà il successivo.
Noi possiamo rispondere con un UIViewController, che verrà visualizzato nella successiva pagina, oppure con “nil” nel caso non vogliamo passare nulla.
Ecco il nostro codice:
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
UIViewController *nextViewController = nil;
if (viewController == self.firstViewController) {
nextViewController = self.secondViewController;
}
return nextViewController;
}
E la spiegazione riga per riga:
- Innanzi tutto creiamo un nuovo view controller, chiamato nextViewController, e lo inizializziamo a nil
- Ora, vediamo quale viewController ci è stato passato. Se è uguale al nostro firstViewController (la prima pagina), allora la pagina successiva sarà secondViewController. Assegniamo questo view controller a nextViewController
- Ritorniamo il nextViewController
Come vedete quello che abbiamo fatto è ritornare la seconda pagina se la pagina attuale è la prima, oppure nil negli altri casi (la pagina attuale è la seconda). Molto semplice.
Il secondo metodo è pageViewController:viewControllerBeforeViewController: ed anch’esso ritorna un UIViewController. Funziona al contrario dell’altro e, passandoci un UIViewController, ci chiede la pagina precedente.
Anche in questo caso posso tornare un “nil” per dirgli che non c’è alcuna pagina precedente.
Notate che l’implementazione è molto simile all’altra.
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
UIViewController *prevViewController = nil;
if (viewController == self.secondViewController) {
prevViewController = self.firstViewController;
}
return prevViewController;
}
Fatto!!!
Salviamo, lanciamo il programma nel simulatore ed ecco il risultato:

Conclusioni
Come avete visto, è più semplice a farsi che a dirsi. Ovviamente questo esempio (trovate il progetto pronto per il download a fondo articolo) è molto semplice ed abbastanza statico, ma con un po’ di fantasia (e perché no, qualche array di ViewControllers) il tutto può diventare estremamente più dinamico e funzionale.
Buon lavoro e buon divertimento
Matteo Cappadonna
Se avete problemi con il progetto presentato, questo è il link per scaricare il nostro esempio.










21 Responses to “T#111 – Come utilizzare UIPageViewController con lo Storyboard”
21 Settembre 2012
astInnanzitutto grazie per il tutorial.
una domanda,
ho provato a inserire una webview invece della label, ma appena faccio questa operazione il passaggio pagine non funziona più, secondo te quale potrebbe essere il motivo?
21 Settembre 2012
Matteo CappadonnaCiao ast,
devo provare a replicare il tuo problema, ma la prima cosa che mi viene in mente è che gli eventi del tocco vengano intercettati dalla webview e non arrivano al UIPageViewController
Potresti provare a disabilitare il flag “User Interaction Enabled” sulla webview e riattivarlo solo a fronte del caricamento di una pagina (per garantire l’interazione con essa).
21 Settembre 2012
astcapito.
ho ridotto le dimensioni della webview e cliccando sulla viewcontroller direttamente cambia pagina regolarmente
ma se volessi inserire un pulsante che fa cambiare pagina è molto complicato?
21 Settembre 2012
Matteo CappadonnaNella sua forma più semplice è abbastanza rapida come cosa. Basta che associ al tasto il seguente codice (supponiamo tu sia nella prima pagina):
[self setViewControllers:@[self.secondViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
Se ti trovi a gestire, invece, più pagine, sarebbe comodo tenere un’array contenente le varie pagine, determinare l’indice della pagina che vuoi caricare con il metodo
indexOfObject:della classe NSArray, vedere se questo indice è successivo o precedente a quello della pagina attualmente visualizzata, ed utilizzare le costanti
UIPageViewControllerNavigationDirectionForwardoppure
UIPageViewControllerNavigationDirectionReverseper visualizzare l’animazione corretta.
21 Settembre 2012
astmesso anche il pulsante che in realtà risolve il problema della webview che non fa cambiare pagina, perchè in realtà trascinando quello la pagina si cambia alla perfezione…bastava una label 😉 …ora…ho provato a farne una versione HD per capire se avevo capito ma non funzia…forse perchè quando ho creato la rottviewController ho flaggato targheted for iPad??
21 Settembre 2012
astrut-view targhettata 😀
21 Settembre 2012
astok adesso funziona anche quello per ipad.
e in effetti levando l’interazione con l’utente come suggerivi fa cambiare pagina anche con la web view.
22 Settembre 2012
AstScusa Matteo,
ho provato a fare come mi dici per assegnare l’azione al bottone, ma non riesco a farlo funzionare,
nel secondViewController.m ho fatto così:
// bottone con un immagine
[_btnImg1 setImage:[UIImage imageWithData: [NSData dataWithContentsOfURL: urlFoto]] forState:UIControlStateNormal];
[self.view addSubview:_btnImg1];
[_btnImg1 addTarget:self action:@selector(nextPage) forControlEvents:UIControlEventTouchUpInside];
poi ho messo nel secondViewController.m il metodo
-(void)nextPage
{
ERRORE -> [self setViewControllers:@[self.firstViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
}
ma mi da errore (li dove ho segnato), dice che non trova firstViewController nell’oggetto di tipo secondViewController…e se oltre ad andare semplicemente nell’altra pagina volessi portarmi appresso un parametro?
rompo troppo? 😀
31 Ottobre 2012
CesareUna domandina stupida stupida! ma se io vorrei il tutto in LANSCAPE invece che in PORTRAIT.
Ho provato a mettere LANDSCAPE nello storyboard e
– (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft);
}
Ma niente da fare.
Un suggerimento Grazie mIlle
Cesare
31 Ottobre 2012
Matteo CappadonnaCiao Cesare,
io ho seguito questi passaggi per farlo funzionare in Landscape:
– Per prima cosa dalla vista progetto ho selezionato “Landscape Left” e “Landscape Right” nella sezione “Supported Interface Orientation”, deselezionando le opzioni di Portrait
– Successivamente, nello storyboard, ho impostato la simulazione delle viste a Landscape su tutte e tre le “Viste” (il PageViewController e le due “pagine”); questo dovrebbe essere solo un pro forma e non impattare sulla reale esecuzione del software sul dispositivo.
– Infine, su TUTTI E TRE I VIEW CONTROLLER, ho modificato il metodo shouldAutorotateToInterfaceOrientation: sostituendo il return con
return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft || interfaceOrientation == UIInterfaceOrientationLandscapeRight);
nota:
Nelle impostazioni del Page View Controller, ho mantenuto la una navigazione ad Horizontal, così da ottenere un’effetto tipo “Album da colorare della Pimpa”.
Spero che sia di aiuto.
Ciao
Matteo
1 Novembre 2012
CesareGrazie tantissimo,
provo subito alle 6 e mezzo del mattino
A proposito complimenti li tutorial … semplice ed efficace.
Cesare
17 Novembre 2012
giovanHo seguito il tutorial ma l’app non viene eseguita perché non riesco ad assegnare l’identifier ai due viewController… nell’inspector non c’è il campo di testo dove inserirlo…..
Qualcuno può aiutarmi?
23 Novembre 2012
giovanScusate ho visto adesso guardando il progetto originale che era storyboard id anziche identifier la casella di testo…
28 Novembre 2012
filippociao a tutti !
qualcuno a mai provato a inserire un pageview controller in un altro view controller ?
mi piacerebbe avere una parte fissa statica nella parte alta dello schermo, e nella parte bassa le pagine che scorrono..
in tutti gli esempi che ho trovato le pagine prendono tutto lo schermo
qualcuno sa se è possibile fare quello che intendo e come ?
grazie in anticipo ! e grazie per i tutorial !
28 Novembre 2012
filipposcusate le a senza h
5 Dicembre 2012
ziocharlySalve a tutti,
Filippo, sto cercando anch’io la stessa cosa…
fammi sapere se trovi un buon esempio
7 Gennaio 2013
Vincenzo ScacciaCiao a tutti,
come prima cosa grazie mille per il tutorial lo trovo molto utile.
Sto cercando di modificare il vostro tutorial per poter creare l’effetto della paginazione su delle immagini che mi arrivano da un server remoto.
Per il momento ho inserito all’interno dei viewcontroller il componente UIImageView e funziona tutto regolarmente.
Ho solamente un dubbio.
Le mie immagini sono 12, quindi seguendo il vostro tutorial dovrei creare 12 ViewController e inserire i riferimenti all’interno della classe RootViewController, che per me sarebbe un normale ViewController, della mia applicazione, purtroppo però ho paura che creare 12 ViewController possa avere dei problemi con la pubblicazione su App Store.
Avete dei suggerimenti su come rendere il tutto più dinamico ?
Grazie,
Vincenzo
21 Maggio 2013
AndreaBuongiorno Matteo
per prima cosa è molto interessante questo tutorial e poi ti volevo chiedere come posso fare se ho una pagina con i bottoni, fai conto come se fossero menù, e quando ne schiaccio uno fa come in modalità libro sempre con la storyboard per l’iphone con la webview o con l’image.
Grazie in anticipo
Andrea
29 Maggio 2013
cristinaContinuate così, bravi!
17 Agosto 2013
LuigiSalve, molto ben fatto.
Ho provato a collegare questo progetto ad un progetto mio assistente, vorrei accedere alla prima pagina del UIPageViewController con un button posizionato nella viewController principale, ho fatto i collegamenti action ecc. ma quando viene premuto il button per far apparire la UIPageViewController, ma l’applicazione va in crash con errore in console NSExepcion dicento che non e collegato allo Storyboard nessun viewController con identifier First Controller. Sapete aiutarmi per risolvere il problema?
17 Ottobre 2013
MarcoCiao Matteo,
io ho la necessità di creare un pageviewcontroller con un numero di view variabile o meglio la view (la sua struttura) è sempre la stessa ma ad ogni cambiamento di pagina deve cambiare il contenuto (ci sarà una table view da caricare dinamicamente ad ogni cambiamento di pagina). Ho letto nelle righe del tuo articolo che in questo caso si dovrebbe utilizzare il Delegate dell’UIPageViewController. Potresti postarmi un esempio? In questo caso nel RootViewController non bisogna dichiarare le View vero? Bisogna fare tutto a runtime?
Sarebbe bello se scrivessi una guida per questo scenario.
Grazie Matteo.
Ciao