Le Size Classes di iOS8

it Indubbiamente, rispetto ad altri grandi cambiamenti come un linguaggio di programmazione completamente nuovo,  alcune novità del nuovo SDK introdotte nell’ultima WWDC (2014) hanno avuto minore risonanza.

Una di queste è sicuramente l’entrata in scena nella piattaforma iOS delle Size Classes: di cosa si tratta?

Adaptive

La parola che meglio descrive l’idea centrale delle migliorie portate all’SDK da iOS8 è Adaptive. Il termine indica la possibilità di scrivere codice che, appunto, si “adatta” a diverse situazioni e che, di conseguenza, è riusabile (tutti sappiamo quanto questo sia importante per noi sviluppatori).

In pratica la filosofia adaptive si concretizza nei seguenti 5 punti cardine del nuovo SDK:

  • Size Classes
  • Adaptive View Controller
  • Adaptive Presentations
  • Adaptive Text e Tables
  • Extensions

In questo post cerco di spiegare il primo punto: Size Classes. Gli altri punti sono ugualmente importanti, ne riparleremo.

Device e orientation

Ad oggi le indicazioni dei nostri designers riguardanti l’interfaccia della app dipendono e si modificano a partire da due elementi fondamentali:

  • la device su cui gira la app (iPad, iPhone)
  • l’orientamento della device stessa (portrait, landscape)

Abbiamo quindi varie combinazioni di queste casistiche e, in futuro, il numero di combinazioni è destinato ad aumentare, infatti già si rumoreggia di nuove device in arrivo da Apple con dimensioni di schermo ancora diverse da quelle esistenti.

In realtà però, pensandoci bene, le modifiche al layout di pagina dipendono solo da due grandezze: la dimensione orizzontale e la dimensione verticale dello schermo.

Fino ad oggi abbiamo scritto molto codice chiedendoci “il corrente orientamento della device”, ma quello che volevamo sapere veramente era “la dimensione della finestra di disegno (canvas)”.

E addirittura per le device abbiamo, sempre fino ad oggi, dovuto mantenere due file diversi di storyboard, uno per l’iPhone e uno per l’iPad. Probabilmente la Apple non se l’è sentita di farci mantenere un terzo storyboard per un’altra device che uscirà quest’autunno, dunque ha affrontato il problema da un punto di vista diverso.

Compact e Regular

In XCode 6 possiamo disegnare la nostra interfaccia ignorando l’informazione “su quale device siamo” o “in quale orientamento siamo” ma basandoci solo sulle dimensioni correnti, o meglio, su certe “fasce” dimensionali. Immaginando di dividere le possibili dimensioni in classi, progettiamo un’interfaccia contenente in se le informazioni utili per adattarsi alle varie classi dimensionali in cui si presenterà.

Le possibili classi dimensionali (size classes) sono:

  • Compact
  • Regular

La dimensione Regular è maggiore di quella Compact. Differenti device e orientamenti corrispondono a dimensioni compact o regular sui due assi, in particolare il seguente schema riassume la situazione per le device esistenti in questo momento:

tabella1

Per esempio un iPhone in orientamento portrait ha la dimensione verticale Regular e orizzontale Compact. L’iPad ha dimensioni Regular sia in portrait che in landscape (attualmente non esiste device con larghezza regular e altezza compact).

Abbiamo spostato il problema chiedendoci se stiamo consumando una coca cola piccola (compact) o grande (regular) e la nostra coca cola sono le dimensioni orizzontale e verticale di iPhone, iPad o … altro.

Interface builder

La maniera migliore per capire queste novità è usare le size classes in XCode (>= 6). Poi vedremo come avvalerci delle size classes anche programmaticamente.

Creiamo un progetto in XCode (Simple View Application – Universal – Swift). Apriamo il Main.storyboard e vediamo che il canvas è quadrato! Non ci siamo abituati, non esiste una device con questa forma. Infatti XCode ci vuole proprio dire che non stiamo progettando per una device in particolare, ma ci stiamo basando sulle size classes.

Notiamo che, avendo creato un progetto nuovo, XCode ci ha automaticamente “iscritti” all’uso delle Size Classes:

optin

Se vogliamo possiamo deselezionare l’uso delle Size Classes in XCode e continuare a lavorare alla vecchia maniera (da sapere: le Size Classes non possono funzionare se non è abilitato anche Auto Layout).

Alcune note sulla compatibilità prima di proseguire: se usiamo solo le funzionalità di Interface Builder la nostra app continua ad essere retro compatibile senza problemi (fino a iOS6), non vengono inserite incompatibilità (ma cambia il formato del file di XCode).

Se invece usiamo da codice le nuove classi introdotte (come UITraitCollection di cui parlerò fra poco) la nostra app sarà compatibile solo con iOS8 (e  scriveremo codice condizionale per renderla retro compatibile).

Come al solito il consiglio è di lavorare per quanto possibile in Interface Builder.

Tornando al nostro progetto, lo storyboard presenta ora, in basso, un bottone w Any h Any.

wany

Premendo il bottone ci viene presentato un popover che indica le size classes per cui valgono le indicazioni di layout che stiamo dando.

Per ogni dimensione abbiamo 3 possibilità di design:

  • solo compact
  • solo regular
  • entrambi (any)

Quindi le possibili combinazioni, considerando altezza e larghezza, sono 9. Questa matrice 3×3 è riportata nel popup mostrato al click del bottone. Nel caso della figura stiamo decidendo di progettare una interfaccia per Any – Any, cioè senza nessuna distinzione particolare (la matrice riporta gli elementi any su riga (altezza) e colonna (larghezza) centrali). 

Possiamo modificare l’indicazione spostando (click and drag) il rettangolo azzurro in diverse configurazioni, per esempio ridimensionando il rettangolo nella prima colonna diciamo a XCode che le indicazioni che stiamo dando sono solo per wCompact hRegular (che al momento corrispondono ad un iPhone in portrait):

wcompact

Notiamo anche che la fascia in basso è diventata blu, indicazione del fatto che stiamo dando regole per un caso particolare (cioè non siamo in wAny hAny).

In genere lavoriamo sulla interfaccia nel seguente modo:  partiamo da wAny hAny e disegniamo l’interfaccia. Poi andiamo a modificare casistiche particolari per casi compact/regular. In questo modo siamo sicuri di gestire tutti i casi possibili e indicare le “eccezioni” per i casi particolari.

Un po’ di pratica

Vediamo nella pratica come usare tutto questo. Nel Main.storyboard inseriamo una ImageView con 2 UILabel (titolo e descrizione) affiancate e 1 UIButton “Dettagli” più in basso con i seguenti constraint:

  • la Image View con un bordo di 20pt dalla main view e in mode: aspect fill
  • il titolo a 40pt-40pt dal top left della main view
  • la descrizione a 20pt in orizzontale dal titolo e larghezza 192pt, e allineata top con il titolo
  • il bottone “Dettagli” a 40pt dal margine sinistro e allineato (bottom) con la descrizione

Il risultato dovrebbe essere il seguente (trovate tutti i dettagli nel codice di esempio alla fine dell’articolo):

fiore

Da XCode possiamo già vedere una anteprima di questo design nelle varie device/orientation. Apriamo l’assistant editor e, dal menu relativo, scegliamo “Preview”:

assistant

Un simbolo ‘+’ nella finestra di preview ci consente di aggiungere un tipo di device, e un simbolo “ruota” (che compare solo on mouse over) sotto ogni device ci permette di esercitare la rotazione per studiare il comportamento dell’interfaccia in vari orientamenti.

(si noti la scritta “English” in basso a dx, queste preview possono essere utilizzate anche per testare l’adattamento del layout alle diverse lingue dell’app!)

Vediamo il risultato per un iPhone in verticale:

preview

Notiamo subito che il nostro design va bene per una larghezza “regular” (sufficientemente grande) ma non per la compact, se infatti ruotiamo il canvas vediamo un layout corretto:

preview_landscape

A questo punto quindi eseguiamo delle modifiche al layout, ma solo nel caso di larghezza compact, per far questo selezioniamo la configurazione dal menu “Class Sizes”:

fiorewcompact

La barra inferiore diventa blu per indicare che stiamo lavorando in un caso particolare (cioè diverso da any – any).

In questa modalità vorremo allineare il testo di descrizione sulla sinistra. Analizziamo il constraint che definisce la posizione orizzontale della descrizione (20 pt di distanza orizzontale dal titolo):

constraintHor1

Premiamo il (secondo) tasto “+” per aggiungere una “Size Class Customization”:

constraintHor2

Scegliamo la configurazione attuale (Compact Width | Regular Height (current)) e poi deselezioniamo il box “Installed” per wC hR:

constraintHor3

Stiamo dicendo al sistema di ignorare questo constraint per la configurazione wC hR.

(Lo stesso risultato potevamo ottenerlo eliminando semplicemente il constraint; infatti questo sarebbe stato cancellato solo dalla configurazione di lavoro corrente (wC hR), vediamo questo modo di procedere nel passo successivo.)

Aggiungiamo ora un constraint che allinea a sinistra la descrizione col testo: questa volta il constraint verrà automaticamente installato solo nella configurazione corrente (wC hR), dato che stiamo lavorando in una configurazione ben precisa (diversa da wAny hAny).

Infine dobbiamo spostare il bottone “Dettagli” più in basso. A scopo dimostrativo lo facciamo lasciando il constraint che allinea i bottom di testo e descrizione e modificando il suo valore (la sua costante). Premiamo stavolta il primo tasto “+” (quello accanto a constant) e inseriamo (per la attuale configurazione: wC hR) il valore 40.

constraintVer

Il risultato finale è il seguente per una larghezza compact:

final

Dunque abbiamo appena realizzato un layout che si comporta diversamente a seconda delle classi di grandezza in cui operiamo. E con un solo storyboard per più device!

Operazioni per-class-size

I due tipi di operazioni al momento eseguibili per-class-size sono:

  • Aggiungere, editare, rimuovere constraints di auto layout
  • Rimuovere o aggiungere view (ma non modificare caratteristiche di una view)

Forse in futuro si potranno fare altre cose, come per esempio avere una UIButton che ha un title in una size class e un altro title in un’altra size class, al momento questo non è possibile, bisogna avere due UIButton diversi e rimuovere uno o l’altro nei due casi.

In realtà è possibile al momento anche un’altra modifica, e cioè l’uso di font diversi in size class diverse.

Provate infatti a sperimentare con il bottone “+” accanto al menu Font nell’inspector della UILabel (credo che nel tempo vedremo aumentare questi bottoni “+” accanto ad altri attributi):

fontChange

UITraitCollection

In iOS8 è stata introdotta una nuova classe che descrive i traits di un certo oggetto di interfaccia (UIWindow, UIViewController, UIView e, in generale, tutti gli oggetti conformi al protocollo UITraitEnvironment). I traits descrivono le classi dimensionali di cui abbiamo parlato finora. In particolare una UITraitCollection ha i seguenti campi:

  • horizontalSizeClass
  • verticalSizeClass
  • displayScale
  • userInterfaceIdiom

Possiamo vedere il nostro ViewController in che modalità sta operando analizzando la sua proprietà traitCollection.

Nel metodo viewDidLoad del nostro view controller stampiamo la trait collection in cui stiamo operando:

Notiamo che è necessario controllare la versione di iOS prima di usare la traitCollection. version è una proprietà del view controller così definita (usiamo NSString invece di String per la conversione a double, al momento non ancora chiara nelle stringhe swift):

(Nella mia attuale versione di XCode è stato necessario aggiungere manualmente UIKit nella sezione “link binary with libraries” in “Build phases” per indicarlo come optional (weak) altrimenti la app in iOS7 non funziona, cioè sembra che Swift non esegua il link weak di default, ma credo che questa sia una caratteristica momentanea dovuta alla rapida evoluzione di Swift e a possibili incompatibilità binario/frameworks; si veda “Binary Compatibility and Frameworks” in questa pagina).

Il metodo printTraitCollection stampa le proprietà della trait collection:

Possiamo anche essere notificati quando i traits cambiano implementando nel view controller il metodo:

Che per esempio viene invocato quando ruotiamo l’iPhone. Avremmo potuto per esempio modificare i constraint programmaticamente al cambio di trait collection, ma è comunque meglio usare l’interfaccia grafica (Interface Builder) anche per problemi di compatibilità con iOS7.

Simulatore resizable

La nuova versione del simulatore disponibile con XCode 6 ci permette di testare la nostra app anche in device (virtuali) al momento non esistenti permettendoci di modificare le dimensioni del simulatore:

simulatore

Nel simulatore possiamo poi definire nella barra in basso le dimensioni della finestra (e le classi associate). Per esempio possiamo lanciare la nostra app in una ipotetica device di dimensioni 500×500 (wR hR):

device 500x500

UIImageAsset

Come ultima nota indichiamo che è anche possibile usare, nella nostra interfaccia, immagini diverse a seconda della size class corrente. Questo può essere ottenuto usando la nuova classe UIImageAsset

Inoltre se nell’Asset Catalog associamo una certa particolare immagine ad una determinata size class, la funzione UIImage(named:”nome_file”) automaticamente ritornerà l’immagine associata con la corrente class size dell’interfaccia.

Credo che siano rari i casi in cui vogliamo dare una immagine diversa per ogni possibile size class, ma ora ci è comunque possibile:

image assets

Codice

Il codice relativo a questo articolo può essere scaricato al seguente link:

Progetto XCode SizeClasses

Reference

Valerio Ferrucci

Valerio Ferrucci (valfer) develops software on Apple Macintosh since the 1990s until today for MacOS, OSX and, since some years, iOS. He is also a Web (PHP/MySQL/JS/CSS) and Android Developer.

More Posts - Website

Follow Me:
TwitterFacebookLinkedIn

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Leave a Reply

Your email address will not be published. Required fields are marked *