Adaptive Layout ist ein Ansatz zum Interface-Layout, bei dem die Anwendung unterschiedliche Layouts für verschiedene Gerätetypen und Bildschirmausrichtungen verwendet. Im Gegensatz zu responsive Design wechselt adaptive Layout zwischen vorbereiteten Layouts an Breakpoints. Dieser Artikel behandelt Size Classes und UITraitCollection in iOS, die Qualifier sw600dp/layout-w600dp in Android und liefert praktische Beispiele.
Wichtige Punkte
Adaptive Layout ist eine UI-Strategie, bei der der Entwickler separate Layouts für jeden Gerätetyp erstellt und basierend auf Bildschirmeigenschaften zwischen ihnen wechselt. Im Gegensatz zu responsive Design, bei dem Elemente fließend übergehen, verwendet adaptive Layout diskretes Umschalten: Telefon — ein Layout, Tablet — ein anderes, Desktop — ein drittes.
Apple führte Size Classes in iOS 8 (2014) zusammen mit universellen Apps (Binärdateien, die sowohl auf iPhone als auch auf iPad laufen) ein. Google stellte den smallestWidth-Qualifier (sw
Die modernen Apple HIG- und Google Material Design-Richtlinien sind sich einig, dass adaptive Layout für Apps, die mehr als eine Bildschirmgröße unterstützen, unerlässlich ist. Google Play markiert das Fehlen von Tablet-Adaption als Mangel. Der App Store hat keine formale Anforderung, aber iPad-Nutzer erwarten ein natives Erlebnis, keine gestreckte iPhone-App.
Size Classes sind eine iOS-Eigenschaft, die den verfügbaren Raum auf jeder Achse als Compact oder Regular definiert. UITraitCollection ist ein iOS-System zur Übergabe von Umgebungseigenschaften durch die View-Hierarchie: Größe, Bildschirmskalierung, Force-Touch-Fähigkeit, Farbskala (hell/dunkel), Barrierefreiheitseinstellungen.
| Gerät / Ausrichtung | Horizontal | Vertikal |
|---|---|---|
| iPhone Hochformat (alle Modelle) | Compact | Regular |
| iPhone Plus/Pro Max Querformat | Regular | Compact |
| iPhone SE / mini Querformat | Compact | Compact |
| iPad Hochformat | Regular | Regular |
| iPad Querformat | Regular | Regular |
| iPad Split View (1/3 Bildschirm) | Compact | Regular |
UITraitCollection wird vom System gebildet und von UIApplication über UIWindow an jede UIView weitergegeben. Bei Änderung der Ausrichtung oder Fenstergröße (Split View) erzeugt das System eine neue UITraitCollection und ruft traitCollectionDidChange auf. In SwiftUI werden Änderungen über Environment Values verfolgt und die View wird automatisch neu gezeichnet.
Die Adaption über UITraitCollection in UIKit wird durch Abonnieren von Trait-Collection-Änderungen und Neuaufbau des Layouts implementiert. Das folgende Beispiel zeigt einen Controller, der zwischen einem vertikalen Stack für iPhone und einem Split-Layout für iPad umschaltet.
// Adaptiver Controller mit 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: Seitenleiste + Inhalt
} else {
showCompactLayout() // iPhone: Tab-Leiste + 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
}
}Die Methode traitCollectionDidChange wird bei jeder Trait-Änderung aufgerufen, aber wir prüfen nur horizontalSizeClass — das bedeutendste Merkmal für Layoutwechsel. Ab iOS 17+ wird empfohlen, UIViewController.horizontalSizeClass als berechnete Eigenschaft für reaktive Updates ohne Abonnement von Änderungen zu verwenden.
Android bietet zwei Hauptbreiten-Qualifier: sw
| Qualifier | Bedingung | Beispielgerät |
|---|---|---|
| layout-sw600dp | Mindestbreite ≥ 600 dp | iPad (768 dp), Pixel C (900 dp) |
| layout-w600dp | Aktuelle Breite ≥ 600 dp | iPad in Split View (600 dp), Telefon im Querformat |
| layout-land | Querformat | Jedes horizontal gedrehte Gerät |
| layout-sw720dp | Mindestbreite ≥ 720 dp | iPad Pro (833 dp), Galaxy Tab S9 (800 dp) |
Ressourcenhierarchie in Android: res/layout/activity_main.xml (Telefon), res/layout-sw600dp/activity_main.xml (7"-Tablet), res/layout-w600dp-land/activity_main.xml (Telefon im Querformat). Android wählt den spezifischsten Qualifier, der zur aktuellen Gerätekonfiguration passt. Wenn keine passende Datei existiert, wird die Basisressource aus res/layout/ verwendet.
Die moderne Android-Entwicklung verwendet Jetpack WindowManager zur Bestimmung der Fenstergröße anstatt Ressourcen direkt zu lesen. Dies ist besonders wichtig für faltbare Geräte, bei denen sich die Bildschirmgröße dynamisch ändert (Galaxy Fold entfaltet sich von 6.2" auf 7.6").
// Adaption über 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("Telefon", 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()
}
}Die Komponente currentWindowAdaptiveInfo() aus der Bibliothek androidx.window passt die Oberfläche an jedes Gerät an: Telefon, Tablet, faltbar, ChromeOS. Breitenklassen: COMPACT (0–600 dp), MEDIUM (600–840 dp), EXPANDED (>840 dp). Höhenklassen: COMPACT (0–480 dp), MEDIUM (480–900 dp), EXPANDED (>900 dp).
SwiftUI bietet eingebaute Werkzeuge für adaptives Layout ohne direkte Verwendung von Size Classes. AnyLayout, ViewThatFits und GeometryReader ermöglichen die Erstellung von Oberflächen, die sich automatisch an den verfügbaren Platz anpassen.
// SwiftUI-Adaption über AnyLayout und 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("Favoriten", systemImage: "star")
Label("Letzte", systemImage: "clock")
Label("Einstellungen", systemImage: "gear")
}
.padding()
}
}
// ViewThatFits — automatische Layout-Auswahl
struct SmartLayout: View {
var body: some View {
ViewThatFits {
HStack { // Priorität 1: horizontal
CardView()
CardView()
}
VStack { // Wenn nicht passt — vertikal
CardView()
CardView()
}
}
}
}ViewThatFits ist ein leistungsstarkes SwiftUI-Werkzeug, das automatisch die erste Child-View auswählt, die ohne Beschneidung in den verfügbaren Platz passt. Dies macht explizite Size Class-Prüfungen für einfache adaptive Wechsel überflüssig. AnyLayout ermöglicht das Umschalten des Layout-Typs (HStack/VStack) ohne if-else im View-Body.
Jetpack Compose verwendet WindowSizeClass (aus der Material 3 Adaptive-Bibliothek) und BoxWithConstraints für adaptives Layout. Im Gegensatz zu XML-Qualifiern bestimmt Compose die Fenstergröße zur Laufzeit und setzt die UI bei Konfigurationsänderungen reaktiv neu zusammen.
// Jetpack Compose adaptiv über BoxWithConstraints
@Composable
fun AdaptiveList(items: List<String>) {
BoxWithConstraints {
val width = maxWidth
if (width >= 600.dp) {
// Tablet: zwei Spalten (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 {
// Telefon: eine Spalte mit Navigation
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 bieten fertige Komponenten: ListDetailScaffold, SupportingPaneScaffold, NavigationSuiteScaffold. Diese Komponenten passen sich automatisch an WindowWidthSizeClass an — sie wechseln zwischen gestapelt (COMPACT) und nebeneinander (MEDIUM/EXPANDED). Der Entwickler muss nur ein Muster auswählen und Content-Panels übergeben.
Häufig gestellte Fragen
sw600dp (smallestWidth) — minimale Bildschirmbreite unabhängig von der Ausrichtung. Wird auf 7"+-Tablets immer aktiviert. w600dp (available width) — verfügbare Breite unter Berücksichtigung der aktuellen Ausrichtung. Wird auf Telefonen im Querformat aktiviert, wo die Breite 600 dp überschreiten kann.
UITraitCollection ist ein iOS-Objekt, das Umgebungseigenschaften enthält: Size Classes (horizontal/vertikal), Bildschirmskalierung, Force-Touch-Fähigkeit, User-Interface-Idiom (iPhone/iPad), Farbskala, Barrierefreiheitseinstellungen. Es wird von UIApplication durch die View-Hierarchie weitergegeben und ändert sich bei Änderung der Ausrichtung oder Fenstergröße.
Verwenden Sie Jetpack WindowManager (Android) und UIScreen nativeBounds + traitCollection (iOS). Faltbare Geräte ändern Size Class beim Aufklappen. Empfohlen wird eine Architektur basierend auf kanonischen Layouts (list-detail, supporting pane) mit reaktivem Abonnement von Fenstergrößenänderungen ohne Activity-Neustart.
Nein. Verwenden Sie ein einzelnes Storyboard mit Size Class-Variationen von Constraints oder programmatisches Layout über UIKit. SwiftUI benötigt überhaupt keine Storyboards. Separate Storyboards für iPad erzeugen Duplikation und erschweren die Wartung — es ist besser, ein einzelnes Layout über Size Classes anzupassen.
Canonical Layouts sind vorgefertigte adaptive Layout-Muster von Google: list-detail (Liste + Detail), supporting pane (Inhalt + Aktionspanel), feed (Karten). Jedes Muster unterstützt drei Zustände (compact/medium/expanded) und ist in der Material 3 Adaptive-Bibliothek durch ListDetailScaffold und SupportingPaneScaffold implementiert.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch