Nelle nostre app utilizziamo abitualmente l’UINavigationController, cioè un container che ci permette di mostrare tutte le nostre sotto-interfacce (UIViewController, o subclass di questa, di seguito VC) come se fossero idealmente disposte in uno stack tramite il quale possiamo navigare fra di esse avanti e indietro (push e pop).
Ad oggi (prima di iOS8) per mostrare, in questa configurazione, un nuovo VC (push) da un altro VC di partenza eseguiamo il seguente codice (objective-C):
1 |
[self.navigationController pushViewController:secondVC animated:YES]; |
dove self è il primo VC che mostra il secondo (secondVC)
Quello che facciamo è tornare al Navigation Controller (classe o subclass di UINavigationController) iniziale che ospita (embed) tutti i VC e chiedere a quest’ultimo di eseguire un push del secondo VC sul suo stack.
Questo procedimento ha lo svantaggio di legarci strettamente ad una precisa configurazione: il VC che esegue questa riga “assume” di essere dentro un Navigation Controller, altrimenti non funziona correttamente: se non siamo in un Navigation Controller la proprietà del VC navigationController è nil e in objective-C un messaggio mandato a nil fa azione nulla (cioè il nuovo VC non appare).
Sarà capitato anche a voi di cambiare le modalità di navigazione della vostra app e, in seguito a questo, aver dovuto modificare il codice di un VC che eseguiva una riga del genere. Cioè un VC è costretto a cambiare il suo comportamento a seconda del modo in cui è stato portato sullo schermo.
Inoltre nella logica Adaptive di iOS8 le interfacce tendono ad essere il più possibile riusabili al cambiare delle condizioni al contorno: dimensioni, device (e quindi organizzazione della navigazione).
Il nuovo sistema iOS8 vuole superare la limitazione precedente, e lo fa aggiungendo due nuovi metodi (e deprecando il vecchio) alla classe UIViewController, cominciamo col primo (e passiamo a swift):
1 |
showViewController(vc: UIViewController?, sender: AnyObject?) |
Con questo metodo la riga iniziale diventa:
1 |
self.showViewController(secondVC, sender: self) |
Questa chiamata è, in un certo senso, UINavigationController-agnostica: risale l’albero di tutti i VC nella gerarchia del corrente (self) fino a quando ne trova uno che “sa eseguire questa azione” (showViewController).
Tale ricerca avviene dietro le quinte usando un nuovo metodo: targetViewControllerForAction. Questo metodo prima verifica se il corrente VC (self) sa eseguire l’azione, altrimenti “cavalca” (a rovescio, verso l’alto) la gerarchia fino a trovare un VC padre che risponda a quella specifica action. Per esempio se si incontra un Navigation Controller, dato che questo risponde all’action showViewController, la ricerca va a buon fine.
Il secondo metodo introdotto è una variante del primo (vedremo subito le differenze) usata al momento da UISplitViewController.
1 |
showDetailViewController(vc: UIViewController?, sender: AnyObject?) |
Questa chiamata viene usata per mostrare un VC “dettaglio” del precedente. Nel caso di showDetailViewController è necessario risalire la gerarchia oltre il Navigation Controller (che non lo implementa) almeno fino ad uno UISplitViewController (o altro VC che lo implementa).
In questo modo il processo si adatta ai diversi view controller containers ed esegue azioni diverse a seconda del contesto in cui si trova (adaptive).
Un view controller può dichiarare di non implementare una certa action usando la canPerformAction. Dal file header di UIViewController:
View controllers can return NO from canPerformAction:withSender: to opt out of being a target for a given action.
E questo probabilmente è quello che fa il Navigation Controller (tornando NO, o meglio false in Swift) nel caso della action showDetailViewController (lo supponiamo, non possiamo esserne sicuri in quanto non abbiamo il codice di UINavigationController).
Vediamo il tutto (come al solito) con degli esempi.
ViewController in Navigation
Nel primo caso ipotizziamo di avere un VC dentro un Navigation Controller. Nella ricerca di un VC che sappia eseguire l’azione showViewController cominciamo da self (e la risposta è no, il semplice UIViewController non la implementa) . Nel risalire la gerarchia quindi il primo VC sopra di noi capace di eseguire l’azione è il Navigation Controller che la implementa sotto forma di push, il risultato è mostrato di seguito:
Video 1: showViewController chiamata da un UIViewController in un UINavigationController
Nel caso di showDetailViewController invece non viene trovato nessun target per questa action e viene eseguita l’azione di default: questa equivale a chiedere al rootViewController della app di eseguire una presentazione modale:
Video 2: showDetailViewController chiamata da un UIViewController in un UINavigationController
ViewController isolato
Se il mio VC non è contenuto in nessuna gerarchia (nessun container sopra di lui) il metodo showViewController e showDetailViewController continuano a funzionare correttamente, infatti il sistema esegue l’azione di default (vedi sopra) e il secondo VC verrà presentato per entrambe le chiamate in maniera modale.
Video 3: showViewController (o showDetailViewController) chiamata da un UIViewController in nessun container
ViewController dentro Detail View Controller di UISplitViewController
Qui la cosa è un po’ più articolata. Senza scendere nei dettagli (scriveremo un post su questo a breve) lo UISplitViewController (di seguito per bervità Split) in iOS8 è universal e perfettamente funzionante anche su iPhone (nelle versioni precedenti funzionava solo su iPad). In particolare può esistere in due stati: expanded (master e detail separate) e collapsed (master e detail in un unica sezione, si comporta in modo simile ad un Navigation Controller). Lo stato viene determinato dalla UITraitCollection corrente determinata dalle class size dell’interfaccia (per i dettagli si veda l’articolo Le Size Classes di iOS8).
Torniamo a noi: lo Split si comporta in maniera diversa a seconda dello stato, vediamo prima il caso in cui un nostro VC sia dentro un detail di uno Split.
Se lo Split è nello stato expanded (due sezioni separate e visibili allo stesso momento), nel risalire l’albero dalla detail incontriamo lo UISplitViewController stesso che esegue l’azione di show eseguendo una replace del nuovo view controller nella sua sezione di detail (a sostituire il vecchio), e questo sia per show che showDetail.
Video 4: showViewController (o showDetailViewController) chiamata dal detail di uno UISplitViewController in stato expanded
Se invece lo SplitViewController è in stato collapsed (per esempio su un iPhone), lo split inoltra la chiamata al suo primary (master) view controller: se questo è un Navigation Controller (come spesso accade) si comporterà come aspettato, cioè eseguendo una push del nuovo view controller (come nel caso 1) nel caso di showViewController.
Video 5: showViewController chiamata dal detail di uno UISplitViewController in stato collapsed
Nel caso di showDetailViewController invece il Navigation Controller non la implementa e quindi il sistema ricorre all’azione di default modale.
Video 6: showDetailViewController chiamata dal detail di uno UISplitViewController in stato collapsed
ViewController in Navigation Controller (master) di UISplitViewController
Se siamo dentro il master dello Split e questo è costituito da un Navigation Controller, il primo container incontrato risalendo l’albero è proprio il Navigation Controller.
Nel caso che lo split sia collapsed, questo equivale per showViewController ad una push del nuovo VC eseguita dal Navigation Controller. Nel caso di showDetailViewController invece il Navigation Controller ignora l’azione che viene eseguita dallo split eseguendo un push nella finestra (il risultato è identico, probabilmente lo Split inoltra una showViewController al Navigation Controller e ):
Video 7: showViewController (o showDetailViewController) chiamata dal master di uno UISplitViewController in stato collapsed
Nel caso expanded invece il comportamento cambia per le due chiamate: con la showViewController il Navigation Controller esegue una push del nuovo VC:
Video 8: showViewController chiamata dal master di uno UISplitViewController in stato expanded
Con la showDetailViewController invece il Navigation Controller ignora la chiamata e lo split esegue una replace del nuovo VC nel suo detail.
Video 9: showDetailViewController chiamata dal master di uno UISplitViewController in stato expanded
Segue
I due nuovi metodi corrispondono esattamente a due nuove segue introdotte in Interface Builder:
E in effetti il progetto di prova (che trovate alla fine) usa queste segue (e non la chiamata da codice) nella maggior parte dei casi.
Tutte le prove sono state eseguite con XCode 6 Beta 4.
Compatibilità
Ovviamente questi due metodi sono disponibili solo da iOS8 in poi. Attenzione quindi a problemi di compatibilità con iOS7.
Nota: nella corrente Beta 4 di Xcode sembra che il 6 non sia più considerato (da Xcode 6 Beta 6 Release Notes):
iOS 7 and OS X 10.9 minimum deployment target: the Swift compiler and Xcode now enforce a minimum deployment target of iOS 7 or OS X
Mavericks. Setting an earlier deployment target results in a build failure.
Conclusioni
Quindi vediamo come questo nuovo modo di portare nuovi view controller sullo schermo si adatti in maniera ottimale a diverse configurazioni.
Inoltre possiamo usare questa flessibilità anche nei nostri Custom View Controller eseguendo un override dei due metodi:
1 2 3 4 |
override func showViewController(vc: UIViewController!, sender: AnyObject!) { } override func showDetailViewController(vc: UIViewController!, sender: AnyObject!) { } |
e avere le stesse possibilità di personalizzazione che abbiamo visto prima per il Navigation Controller e lo Split Controller.
Codice
Il codice relativo a questo articolo può essere scaricato al seguente link:
Reference