Adaptive Layout — přístup k tvorbě rozhraní, při kterém aplikace používá různé rozvržení pro různé typy zařízení a orientace obrazovky. Na rozdíl od responsive designu adaptive layout přepíná mezi předem připravenými rozvrženími v bodech breakpoint. Článek rozebírá Size Classes a UITraitCollection v iOS, kvalifikátory sw600dp/layout-w600dp v Androidu a uvádí praktické příklady.
Hlavní body
Adaptive Layout — strategie vytváření rozhraní, při které vývojář vytváří samostatná rozvržení pro každý typ zařízení a přepíná mezi nimi na základě charakteristik obrazovky. Na rozdíl od responsive designu, kde prvky plynule přecházejí, adaptive layout používá diskrétní přepínání: telefon — jedno rozvržení, tablet — jiné, desktop — třetí.
Apple zavedl Size Classes v iOS 8 (2014) spolu s univerzálními aplikacemi (universal binaries fungujícími na iPhone i iPad). Google představil kvalifikátor smallestWidth (sw
Moderní doporučení Apple HIG a Google Material Design se shodují, že adaptive layout je povinný pro aplikace podporující více než jednu velikost obrazovky. Google Play označuje chybějící adaptaci pro tablety jako nedostatek. App Store nemá formální požadavek, ale uživatelé iPadu očekávají nativní zážitek, ne nataženou aplikaci pro iPhone.
Size Classes — charakteristika iOS definující dostupný prostor jako Compact nebo Regular na každé ose. UITraitCollection — systém iOS pro předávání charakteristik prostředí přes hierarchii view: velikost, měřítko obrazovky, force touch capability, barevný gamut (light/dark), accessibility settings.
| Zařízení / Orientace | Horizontal | Vertical |
|---|---|---|
| iPhone portrét (všechny modely) | Compact | Regular |
| iPhone Plus/Pro Max na šířku | Regular | Compact |
| iPhone SE / mini na šířku | Compact | Compact |
| iPad portrét | Regular | Regular |
| iPad na šířku | Regular | Regular |
| iPad Split View (1/3 obrazovky) | Compact | Regular |
UITraitCollection je tvořen systémem a předáván od UIApplication přes UIWindow ke každému UIView. Při změně orientace nebo velikosti okna (Split View) systém vygeneruje nový UITraitCollection a zavolá traitCollectionDidChange. Ve SwiftUI jsou změny sledovány přes Environment Values a automaticky překreslují view.
Adaptace přes UITraitCollection v UIKit je implementována přihlášením k odběru změn trait collection a přestavbou rozvržení. Níže uvedený příklad ukazuje kontroler, který přepíná mezi vertikálním stackem pro iPhone a split rozvržením pro iPad.
// Adaptivní kontroler s 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: postranní panel + obsah
} else {
showCompactLayout() // iPhone: panel karet + 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
}
}Metoda traitCollectionDidChange je volána při každé změně traits, ale kontrolujeme pouze horizontalSizeClass — nejvýznamnější indikátor pro změnu rozvržení. V iOS 17+ se doporučuje používat UIViewController.horizontalSizeClass jako computed property pro reaktivní aktualizaci bez přihlášení k odběru změn.
Android poskytuje dva hlavní kvalifikátory šířky: sw
| Kvalifikátor | Podmínka | Příklad zařízení |
|---|---|---|
| layout-sw600dp | Minimální šířka ≥ 600 dp | iPad (768 dp), Pixel C (900 dp) |
| layout-w600dp | Aktuální šířka ≥ 600 dp | iPad v Split View (600 dp), telefon na šířku |
| layout-land | Orientace na šířku | Jakékoli zařízení otočené horizontálně |
| layout-sw720dp | Minimální šířka ≥ 720 dp | iPad Pro (833 dp), Galaxy Tab S9 (800 dp) |
Hierarchie zdrojů v Androidu: res/layout/activity_main.xml (telefon), res/layout-sw600dp/activity_main.xml (tablet 7"), res/layout-w600dp-land/activity_main.xml (telefon na šířku). Android vybírá nejkonkrétnější kvalifikátor odpovídající aktuální konfiguraci zařízení. Při neexistenci vhodného souboru se použije základní zdroj z res/layout/.
Moderní vývoj pro Android používá Jetpack WindowManager k určení velikosti okna namísto přímého čtení zdrojů. To je důležité zejména pro skládací zařízení, kde se velikost obrazovky může dynamicky měnit (Galaxy Fold se rozkládá z 6,2" na 7,6").
// Adaptace přes 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()
}
}Komponenta currentWindowAdaptiveInfo() z knihovny androidx.window přizpůsobuje rozhraní libovolnému zařízení: telefon, tablet, skládací, ChromeOS. Třídy šířky: COMPACT (0–600 dp), MEDIUM (600–840 dp), EXPANDED (>840 dp). Třídy výšky: COMPACT (0–480 dp), MEDIUM (480–900 dp), EXPANDED (>900 dp).
SwiftUI poskytuje vestavěné nástroje pro adaptivní rozvržení bez přímého použití Size Classes. AnyLayout, ViewThatFits a GeometryReader umožňují vytvářet rozhraní, která se automaticky přizpůsobují dostupnému prostoru.
// Adaptace SwiftUI přes AnyLayout a 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("Oblíbené", systemImage: "star")
Label("Nedávné", systemImage: "clock")
Label("Nastavení", systemImage: "gear")
}
.padding()
}
}
// ViewThatFits — automatický výběr rozvržení
struct SmartLayout: View {
var body: some View {
ViewThatFits {
HStack { // Priorita 1: horizontální
CardView()
CardView()
}
VStack { // Pokud se nevejde — vertikální
CardView()
CardView()
}
}
}
}ViewThatFits — výkonný nástroj SwiftUI, který automaticky vybírá první podřízený view, který se vejde do dostupného prostoru bez oříznutí. To eliminuje potřebu explicitní kontroly Size Classes pro jednoduchá adaptivní přepínání. AnyLayout umožňuje změnit typ rozvržení (HStack/VStack) bez if-else v těle view.
Jetpack Compose používá WindowSizeClass (z knihovny Material 3 adaptive) a BoxWithConstraints pro adaptivní rozvržení. Na rozdíl od XML kvalifikátorů Compose určuje velikost okna za běhu a reaktivně překresluje UI při změně konfigurace.
// Adaptace Jetpack Compose přes BoxWithConstraints
@Composable
fun AdaptiveList(items: List<String>) {
BoxWithConstraints {
val width = maxWidth
if (width >= 600.dp) {
// Tablet: dva sloupce (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: jeden sloupec s navigací
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 poskytuje hotové komponenty: ListDetailScaffold, SupportingPaneScaffold, NavigationSuiteScaffold. Tyto komponenty se automaticky přizpůsobují WindowWidthSizeClass — přepínají mezi stacked (COMPACT) a side-by-side (MEDIUM/EXPANDED). Vývojář stačí vybrat vzor a předat obsahové panely.
Často kladené otázky
sw600dp (smallestWidth) — minimální šířka obrazovky nezávisle na orientaci. Vždy funguje na tabletech 7"+. w600dp (available width) — dostupná šířka s ohledem na aktuální orientaci. Funguje na telefonech na šířku, kde šířka může přesáhnout 600 dp.
UITraitCollection — iOS objekt obsahující charakteristiky prostředí: size classes (horizontal/vertical), display scale, force touch capability, user interface idiom (iPhone/iPad), color gamut, accessibility settings. Je předáván od UIApplication přes hierarchii view a mění se při změně orientace nebo velikosti okna.
Použijte Jetpack WindowManager (Android) a UIScreen nativeBounds + traitCollection (iOS). Skládací zařízení mění Size Class při rozložení. Doporučuje se architektura založená na kanonických rozvrženích (list-detail, supporting pane) s reaktivním přihlášením ke změnám velikosti okna bez restartování Activity.
Ne. Použijte jeden storyboard s Size Classes variacemi constraintů nebo programové rozvržení přes UIKit. SwiftUI storyboardy vůbec nevyžaduje. Samostatné storyboardy pro iPad vytvářejí duplicitu a komplikují údržbu — je lepší přizpůsobit jedno rozvržení přes Size Classes.
Canonical layouts — hotové vzory adaptivního rozvržení od Google: list-detail (seznam + podrobnosti), supporting pane (obsah + panel akcí), feed (karty). Každý vzor podporuje tři stavy (compact/medium/expanded) a je implementován v knihovně Material 3 Adaptive přes ListDetailScaffold a SupportingPaneScaffold.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také