Adaptive Layout: concetti chiave, classi di dimensione e qualificatori

Autore: IT Sectr Pubblicato: 2026-02-26 Tempo di lettura: 10 min

Adaptive Layout è un approccio al layout delle interfacce in cui l'applicazione utilizza layout diversi per diversi tipi di dispositivi e orientamenti dello schermo. A differenza del responsive design, l'adaptive layout passa da un layout all'altro tra layout predefiniti in punti di breakpoint. Questo articolo analizza Size Classes e UITraitCollection in iOS, i qualificatori sw600dp/layout-w600dp in Android e fornisce esempi pratici.

Punti chiave

  • Adaptive Layout — commutazione discreta dei layout in base a dimensione dello schermo, orientamento e tipo di dispositivo
  • Size Classes in iOS classificano lo spazio come Compact o Regular orizzontalmente e verticalmente
  • UITraitCollection — contenitore di caratteristiche ambientali iOS (dimensione, scala, force touch, gamma colori)
  • sw600dp — qualificatore Android di larghezza minima per tablet (7" e oltre)
  • layout-w600dp — qualificatore di larghezza disponibile che si attiva quando c'è spazio sufficiente nell'orientamento corrente

Cos'è Adaptive Layout?

Adaptive Layout è una strategia UI in cui lo sviluppatore crea layout separati per ogni tipo di dispositivo e commuta tra di essi in base alle caratteristiche dello schermo. A differenza del responsive design dove gli elementi fluiscono armoniosamente, l'adaptive layout utilizza commutazioni discrete: telefono — un layout, tablet — un altro, desktop — un terzo.

Apple ha introdotto Size Classes in iOS 8 (2014) insieme alle applicazioni universali (binari funzionanti sia su iPhone che su iPad). Google ha presentato il qualificatore smallestWidth (swdp) in Android 3.2 (2011). Entrambi i meccanismi risolvono lo stesso problema: dare allo sviluppatore uno strumento per creare diverse UI per diversi schermi senza duplicazione di codice.

Le linee guida moderne di Apple HIG e Google Material Design concordano che l'adaptive layout è essenziale per le applicazioni che supportano più di una dimensione dello schermo. Google Play segnala la mancanza di adattamento ai tablet come un difetto. L'App Store non ha un requisito formale, ma gli utenti iPad si aspettano un'esperienza nativa, non un'applicazione iPhone allungata.

Size Classes e UITraitCollection in iOS

Size Classes sono una caratteristica iOS che definisce lo spazio disponibile come Compact o Regular su ciascun asse. UITraitCollection è un sistema iOS per passare le caratteristiche ambientali attraverso la gerarchia delle view: dimensione, scala dello schermo, capacità force touch, gamma colori (chiaro/scuro), impostazioni di accessibilità.

Dispositivo / OrientamentoOrizzontaleVerticale
iPhone ritratto (tutti i modelli)CompactRegular
iPhone Plus/Pro Max paesaggioRegularCompact
iPhone SE / mini paesaggioCompactCompact
iPad ritrattoRegularRegular
iPad paesaggioRegularRegular
iPad Split View (1/3 schermo)CompactRegular

UITraitCollection è formato dal sistema e passato da UIApplication attraverso UIWindow a ogni UIView. Quando l'orientamento o la dimensione della finestra (Split View) cambia, il sistema genera un nuovo UITraitCollection e chiama traitCollectionDidChange. In SwiftUI, i cambiamenti vengono tracciati tramite Environment Values e la view viene ridisegnata automaticamente.

Esempio di layout adattivo in Swift

L'adattamento tramite UITraitCollection in UIKit è implementato sottoscrivendo i cambiamenti di trait collection e ricostruendo il layout. L'esempio seguente mostra un controller che commuta tra uno stack verticale per iPhone e un layout diviso per iPad.

swift
// Controller adattivo con Size Classes
import UIKit

final class AdaptiveViewController: UIViewController {

    private let compactStack = UIStackView()
    private let regularStack = UIStackView()
    private let sidebar = UIView()
    private let content = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupViews()
        updateLayout(for: traitCollection)
    }

    override func traitCollectionDidChange(
        _ previousTraitCollection: UITraitCollection?
    ) {
        super.traitCollectionDidChange(previousTraitCollection)
        if traitCollection.horizontalSizeClass
            != previousTraitCollection?.horizontalSizeClass {
            updateLayout(for: traitCollection)
        }
    }

    private func updateLayout(for traits: UITraitCollection) {
        if traits.horizontalSizeClass == .regular {
            showRegularLayout() // iPad: barra laterale + contenuto
        } else {
            showCompactLayout() // iPhone: barra delle schede + push
        }
    }

    private func showRegularLayout() {
        view.subviews.forEach { $0.removeFromSuperview() }
        regularStack.addArrangedSubview(sidebar)
        regularStack.addArrangedSubview(content)
        regularStack.frame = view.bounds
        regularStack.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(regularStack)
    }

    private func showCompactLayout() {
        view.subviews.forEach { $0.removeFromSuperview() }
        compactStack.addArrangedSubview(content)
        compactStack.frame = view.bounds
        compactStack.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(compactStack)
    }

    private func setupViews() {
        sidebar.backgroundColor = .systemGray6
        content.backgroundColor = .systemBackground
        compactStack.axis = .vertical
        regularStack.axis = .horizontal
    }
}

Il metodo traitCollectionDidChange viene chiamato a qualsiasi cambiamento di traits, ma controlliamo solo horizontalSizeClass — l'indicatore più significativo per il cambio di layout. In iOS 17+ si raccomanda di usare UIViewController.horizontalSizeClass come proprietà calcolata per aggiornamenti reattivi senza sottoscrizione ai cambiamenti.

Qualificatori Android: sw600dp e w600dp

Android fornisce due qualificatori principali di larghezza: swdp (smallestWidth) e wdp (available width). La differenza è critica: sw è la larghezza minima dello schermo indipendentemente dall'orientamento, w è la larghezza disponibile considerando l'orientamento corrente. sw garantisce che il layout venga caricato per un dispositivo che fisicamente ha la larghezza minima specificata. w risponde alla rotazione — in paesaggio, w600dp può attivarsi anche se la larghezza in ritratto è di 360 dp.

QualificatoreCondizioneEsempio di dispositivo
layout-sw600dpLarghezza minima ≥ 600 dpiPad (768 dp), Pixel C (900 dp)
layout-w600dpLarghezza corrente ≥ 600 dpiPad in Split View (600 dp), telefono in paesaggio
layout-landOrientamento landscapeQualsiasi dispositivo ruotato orizzontalmente
layout-sw720dpLarghezza minima ≥ 720 dpiPad Pro (833 dp), Galaxy Tab S9 (800 dp)

Gerarchia delle risorse in Android: res/layout/activity_main.xml (telefono), res/layout-sw600dp/activity_main.xml (tablet 7"), res/layout-w600dp-land/activity_main.xml (telefono in paesaggio). Android seleziona il qualificatore più specifico che corrisponde alla configurazione corrente del dispositivo. Quando non esiste un file corrispondente, viene utilizzata la risorsa base da res/layout/.

Esempio di layout adattivo in Kotlin

Lo sviluppo Android moderno utilizza Jetpack WindowManager per determinare la dimensione della finestra invece di leggere direttamente le risorse. Questo è particolarmente importante per i dispositivi pieghevoli dove la dimensione dello schermo cambia dinamicamente (Galaxy Fold si apre da 6.2" a 7.6").

kotlin
// Adattamento tramite Jetpack WindowManager
import androidx.window.core.layout.WindowHeightSizeClass
import androidx.window.core.layout.WindowSizeClass
import androidx.window.core.layout.WindowWidthSizeClass

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun AdaptiveScreen() {
    val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass

    FlowRow(
        verticalAlignment = Arrangement.Top,
        horizontalArrangement = when (windowSizeClass.windowWidthSizeClass) {
            WindowWidthSizeClass.EXPANDED -> Arrangement.SpaceEvenly
            else -> Arrangement.Start
        }
    ) {
        when (windowSizeClass.windowWidthSizeClass) {
            WindowWidthSizeClass.COMPACT -> {
                CompactContent()
            }
            WindowWidthSizeClass.MEDIUM -> {
                MediumContent()
            }
            WindowWidthSizeClass.EXPANDED -> {
                ExpandedContent()
            }
        }
    }
}

@Composable
private fun CompactContent() {
    Column(modifier = Modifier.padding(16.dp)) {
        Text("Telefono", style = MaterialTheme.typography.headlineSmall)
        ListContent()
    }
}

@Composable
private fun MediumContent() {
    Row(modifier = Modifier.padding(24.dp)) {
        NavigationRail { ... }
        Column { ListContent() }
    }
}

@Composable
private fun ExpandedContent() {
    Row(modifier = Modifier.padding(32.dp)) {
        PermanentNavigationDrawer { ... }
        Column {
            ListContent()
        }
        DetailPane()
    }
}

Il componente currentWindowAdaptiveInfo() dalla libreria androidx.window adatta l'interfaccia a qualsiasi dispositivo: telefono, tablet, pieghevole, ChromeOS. Classi di larghezza: COMPACT (0–600 dp), MEDIUM (600–840 dp), EXPANDED (>840 dp). Classi di altezza: COMPACT (0–480 dp), MEDIUM (480–900 dp), EXPANDED (>900 dp).

Adaptive Layout in SwiftUI

SwiftUI fornisce strumenti integrati per il layout adattivo senza usare direttamente Size Classes. AnyLayout, ViewThatFits e GeometryReader permettono di costruire interfacce che si adattano automaticamente allo spazio disponibile.

swift
// Adattamento SwiftUI tramite AnyLayout e ViewThatFits
import SwiftUI

struct AdaptiveGrid: View {
    @Environment(\.horizontalSizeClass) private var hSizeClass

    var body: some View {
        let layout = hSizeClass == .regular
            ? AnyLayout(HStackLayout())
            : AnyLayout(VStackLayout())

        layout {
            Label("Preferiti", systemImage: "star")
            Label("Recenti", systemImage: "clock")
            Label("Impostazioni", systemImage: "gear")
        }
        .padding()
    }
}

// ViewThatFits — selezione automatica del layout
struct SmartLayout: View {
    var body: some View {
        ViewThatFits {
            HStack { // Priorità 1: orizzontale
                CardView()
                CardView()
            }
            VStack { // Se non ci sta — verticale
                CardView()
                CardView()
            }
        }
    }
}

ViewThatFits è un potente strumento di SwiftUI che seleziona automaticamente la prima view figlia che si adatta allo spazio disponibile senza tagli. Questo elimina la necessità di controllare esplicitamente Size Classes per commutazioni adattive semplici. AnyLayout permette di cambiare il tipo di layout (HStack/VStack) senza if-else nel corpo della view.

Adaptive Layout in Jetpack Compose

Jetpack Compose utilizza WindowSizeClass (dalla libreria Material 3 adaptive) e BoxWithConstraints per il layout adattivo. A differenza dei qualificatori XML, Compose determina la dimensione della finestra in runtime e ricompone l'UI reattivamente ai cambiamenti di configurazione.

kotlin
// Jetpack Compose adattivo tramite BoxWithConstraints
@Composable
fun AdaptiveList(items: List<String>) {
    BoxWithConstraints {
        val width = maxWidth

        if (width >= 600.dp) {
            // Tablet: due colonne (list-detail)
            Row(modifier = Modifier.fillMaxSize()) {
                LazyColumn(modifier = Modifier.weight(1f)) {
                    items(items) { item ->
                        ListItem(text = item)
                    }
                }
                var selected by remember { mutableStateOf(items.first()) }
                DetailPanel(item = selected)
                    .weight(2f)
            }
        } else {
            // Telefono: una colonna con navigazione
            LazyColumn(modifier = Modifier.fillMaxSize()) {
                items(items) { item ->
                    ListItem(text = item, onClick = { navigateToDetail(item) })
                }
            }
        }
    }
}

@Composable
fun AdaptiveListMaterial3() {
    val windowClass = currentWindowAdaptiveInfo().windowSizeClass

    AdaptiveLayout(
        layout = windowClass.windowWidthSizeClass
    ) {
        when (windowClass.windowWidthSizeClass) {
            WindowWidthSizeClass.COMPACT -> {
                ListDetailScaffold(
                    isDetailOnly = false,
                    list = { ListPane() },
                    detail = { DetailPane() }
                )
            }
            else -> {
                ListDetailScaffold(
                    isDetailOnly = false,
                    list = { ListPane() },
                    detail = { DetailPane() }
                )
            }
        }
    }
}

Material 3 Adaptive Layouts fornisce componenti pronti: ListDetailScaffold, SupportingPaneScaffold, NavigationSuiteScaffold. Questi componenti si adattano automaticamente a WindowWidthSizeClass — commutando tra impilato (COMPACT) e affiancato (MEDIUM/EXPANDED). Lo sviluppatore deve solo scegliere un pattern e passare i pannelli di contenuto.

Domande frequenti

Qual è la differenza tra sw600dp e w600dp?

sw600dp (smallestWidth) — larghezza minima dello schermo indipendentemente dall'orientamento. Si attiva sempre sui tablet 7"+. w600dp (available width) — larghezza disponibile considerando l'orientamento corrente. Si attiva sui telefoni in paesaggio dove la larghezza può superare 600 dp.

Cos'è UITraitCollection in iOS?

UITraitCollection è un oggetto iOS che contiene le caratteristiche ambientali: size classes (orizzontale/verticale), scala dello schermo, capacità force touch, idioma dell'interfaccia utente (iPhone/iPad), gamma colori, impostazioni di accessibilità. Viene passato da UIApplication attraverso la gerarchia delle view e cambia quando cambiano l'orientamento o la dimensione della finestra.

Come adattare un'app ai dispositivi pieghevoli?

Utilizzare Jetpack WindowManager (Android) e UIScreen nativeBounds + traitCollection (iOS). I dispositivi pieghevoli cambiano Size Class quando vengono aperti. Si raccomanda un'architettura basata su layout canonici (list-detail, supporting pane) con sottoscrizione reattiva ai cambiamenti di dimensione della finestra senza riavviare l'Activity.

Devo creare storyboard separati per iPad?

No. Utilizzare un unico storyboard con variazioni di constraint per Size Class o layout programmatico tramite UIKit. SwiftUI non richiede affatto storyboard. Storyboard separati per iPad creano duplicazione e complicano la manutenzione — è meglio adattare un unico layout attraverso Size Classes.

Cosa sono i layout canonici in Material 3?

Canonical layouts sono pattern di layout adattivo predefiniti di Google: list-detail (lista + dettaglio), supporting pane (contenuto + pannello azioni), feed (schede). Ogni pattern supporta tre stati (compact/medium/expanded) ed è implementato nella libreria Material 3 Adaptive tramite ListDetailScaffold e SupportingPaneScaffold.

Riepilogo

  • Adaptive Layout — commutazione discreta dei layout ai breakpoint per diversi dispositivi
  • Size Classes iOS (Compact/Regular) definiscono il layout per iPhone, iPad e Split View
  • UITraitCollection — meccanismo per passare le caratteristiche ambientali attraverso la gerarchia delle view
  • sw600dp — qualificatore Android di larghezza minima per tablet 7"+
  • w600dp — qualificatore di larghezza disponibile che si attiva ruotando in paesaggio
  • Jetpack WindowManager fornisce WindowSizeClass (WindowWidthSizeClass: COMPACT/MEDIUM/EXPANDED)
  • Material 3 Adaptive — componenti pronti (ListDetailScaffold) con adattamento automatico

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche