Adaptive Layout: ключові поняття, класи розмірів та кваліфікатори

Автор: IT Sectr Опубліковано: 2026-02-26 Час читання: 10 хв

Adaptive Layout — підхід до верстки інтерфейсів, при якому додаток використовує різні макети для різних типів пристроїв та орієнтацій екрана. На відміну від responsive design, adaptive layout перемикається між заздалегідь підготовленими макетами в точках breakpoint. Стаття розбирає Size Classes та UITraitCollection в iOS, кваліфікатори sw600dp/layout-w600dp в Android та наводить практичні приклади.

Головне

  • Adaptive Layout — дискретне перемикання макетів залежно від розміру екрана, орієнтації та типу пристрою
  • Size Classes в iOS класифікують простір як Compact або Regular по горизонталі та вертикалі
  • UITraitCollection — контейнер характеристик середовища iOS (розмір, масштаб, force touch, колірна гамма)
  • sw600dp — Android-кваліфікатор мінімальної ширини для планшетів (7" і більше)
  • layout-w600dp — кваліфікатор доступної ширини, що спрацьовує при достатньому просторі в поточній орієнтації

Що таке Adaptive Layout?

Adaptive Layout — стратегія побудови інтерфейсу, при якій розробник створює окремі макети для кожного типу пристроїв і перемикається між ними на основі характеристик екрана. На відміну від responsive design, де елементи плавно перетікають, adaptive layout використовує дискретні перемикання: телефон — один макет, планшет — інший, десктоп — третій.

Apple впровадила Size Classes у iOS 8 (2014) разом з універсальними додатками (universal binaries, що працюють і на iPhone, і на iPad). Google представила кваліфікатор smallestWidth (swdp) у Android 3.2 (2011). Обидва механізми вирішують одне завдання: дати розробнику інструмент для створення різних UI для різних екранів без дублювання коду.

Сучасні рекомендації Apple HIG та Google Material Design сходяться на тому, що adaptive layout обов'язковий для додатків, що підтримують більше одного розміру екрана. Google Play позначає відсутність адаптації під планшети як недолік. App Store не має формальної вимоги, але користувачі iPad очікують нативного досвіду, а не розтягнутого iPhone-додатка.

iOS Size Classes та UITraitCollection

Size Classes — характеристика iOS, що визначає доступний простір як компактний (Compact) або регулярний (Regular) по кожній осі. UITraitCollection — система iOS для передачі характеристик середовища через ієрархію view: розмір, масштаб екрана, force touch capability, колірна гамма (світла/темна), accessibility settings.

Пристрій / ОрієнтаціяГоризонтальВертикаль
iPhone портрет (всі моделі)CompactRegular
iPhone Plus/Pro Max ландшафтRegularCompact
iPhone SE / mini ландшафтCompactCompact
iPad портретRegularRegular
iPad ландшафтRegularRegular
iPad Split View (1/3 екрана)CompactRegular

UITraitCollection формується системою і передається від UIApplication через UIWindow до кожного UIView. При зміні орієнтації або розміру вікна (Split View) система генерує новий UITraitCollection і викликає traitCollectionDidChange. У SwiftUI зміни відстежуються через Environment Values і автоматично перемальовують view.

Приклад адаптивного layout на Swift

Адаптація через UITraitCollection в UIKit реалізується підпискою на зміни trait collection і перебудовою layout. Приклад нижче показує контролер, який перемикається між вертикальним стеком для iPhone і split-макетом для iPad.

swift
// Адаптивний контролер з 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: sidebar + content
        } else {
            showCompactLayout() // iPhone: tab bar + 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
    }
}

Метод traitCollectionDidChange викликається при будь-якій зміні traits, але ми перевіряємо тільки horizontalSizeClass — найбільш значущу ознаку для зміни макета. В iOS 17+ рекомендується використовувати UIViewController.horizontalSizeClass як computed property для реактивного оновлення без підписки на зміни.

Android кваліфікатори: sw600dp та w600dp

Android надає два основних кваліфікатори ширини: swdp (smallestWidth) та wdp (available width). Різниця критична: sw — мінімальна ширина екрана незалежно від орієнтації, w — доступна ширина з урахуванням поточної орієнтації. sw гарантує, що layout завантажиться для пристрою, який фізично має вказану мінімальну ширину. w реагує на поворот — при ландшафті може спрацювати w600dp, навіть якщо в портреті ширина 360 dp.

КваліфікаторУмоваПриклад пристрою
layout-sw600dpМінімальна ширина ≥ 600 dpiPad (768 dp), Pixel C (900 dp)
layout-w600dpПоточна ширина ≥ 600 dpiPad у Split View (600 dp), телефон у ландшафті
layout-landОрієнтація landscapeБудь-який пристрій повернуто горизонтально
layout-sw720dpМінімальна ширина ≥ 720 dpiPad Pro (833 dp), Galaxy Tab S9 (800 dp)

Ієрархія ресурсів в Android: res/layout/activity_main.xml (телефон), res/layout-sw600dp/activity_main.xml (планшет 7"), res/layout-w600dp-land/activity_main.xml (телефон у ландшафті). Android вибирає найбільш специфічний кваліфікатор, що підходить під поточну конфігурацію пристрою. За відсутності відповідного файлу використовується базовий ресурс з res/layout/.

Приклад адаптивного layout на Kotlin

Сучасна Android-розробка використовує Jetpack WindowManager для визначення розміру вікна замість прямого читання ресурсів. Це особливо важливо для складних пристроїв, де розмір екрана може змінюватися динамічно (Galaxy Fold розкладається з 6.2" до 7.6").

kotlin
// Адаптація через 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("Телефон", 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()
    }
}

Компонент currentWindowAdaptiveInfo() з бібліотеки androidx.window адаптує інтерфейс під будь-який пристрій: телефон, планшет, складний, ChromeOS. Класи ширини: COMPACT (0–600 dp), MEDIUM (600–840 dp), EXPANDED (>840 dp). Класи висоти: COMPACT (0–480 dp), MEDIUM (480–900 dp), EXPANDED (>900 dp).

Adaptive Layout у SwiftUI

SwiftUI надає вбудовані інструменти для адаптивного layout без прямого використання Size Classes. AnyLayout, ViewThatFits та GeometryReader дозволяють будувати інтерфейси, що автоматично підлаштовуються під доступний простір.

swift
// SwiftUI адаптація через AnyLayout та 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("Обране", systemImage: "star")
            Label("Недавні", systemImage: "clock")
            Label("Налаштування", systemImage: "gear")
        }
        .padding()
    }
}

// ViewThatFits — автоматичний вибір layout
struct SmartLayout: View {
    var body: some View {
        ViewThatFits {
            HStack { // Пріоритет 1: горизонтальний
                CardView()
                CardView()
            }
            VStack { // Якщо не влізло — вертикальний
                CardView()
                CardView()
            }
        }
    }
}

ViewThatFits — потужний інструмент SwiftUI, що автоматично вибирає перший дочірній view, який поміщається в доступний простір без обрізання. Це позбавляє від необхідності явно перевіряти Size Classes для простих адаптивних перемикань. AnyLayout дозволяє перемикати тип layout (HStack/VStack) без if-else в тілі view.

Adaptive Layout у Jetpack Compose

Jetpack Compose використовує WindowSizeClass (з бібліотеки Material 3 adaptive) та BoxWithConstraints для адаптивної верстки. На відміну від XML-кваліфікаторів, Compose визначає розмір вікна в runtime і перекомпоновує UI реактивно при зміні конфігурації.

kotlin
// Jetpack Compose adaptive через BoxWithConstraints
@Composable
fun AdaptiveList(items: List<String>) {
    BoxWithConstraints {
        val width = maxWidth

        if (width >= 600.dp) {
            // Планшет: дві колонки (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 {
            // Телефон: одна колонка з навігацією
            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 надають готові компоненти: ListDetailScaffold, SupportingPaneScaffold, NavigationSuiteScaffold. Ці компоненти автоматично адаптуються під WindowWidthSizeClass — перемикаються між stacked (COMPACT) та side-by-side (MEDIUM/EXPANDED). Розробнику достатньо вибрати патерн і передати контентні панелі.

Часті запитання

У чому різниця між sw600dp та w600dp?

sw600dp (smallestWidth) — мінімальна ширина екрана незалежно від орієнтації. Спрацьовує на планшетах 7"+ завжди. w600dp (available width) — доступна ширина з урахуванням поточної орієнтації. Спрацьовує на телефонах у ландшафті, де ширина може перевищувати 600 dp.

Що таке UITraitCollection в iOS?

UITraitCollection — об'єкт iOS, що містить характеристики середовища: size classes (horizontal/vertical), display scale, force touch capability, user interface idiom (iPhone/iPad), color gamut, accessibility settings. Передається від UIApplication через ієрархію view і змінюється при зміні орієнтації або розміру вікна.

Як адаптувати додаток під складні пристрої?

Використовуйте Jetpack WindowManager (Android) та UIScreen nativeBounds + traitCollection (iOS). Складні пристрої змінюють Size Class при розкладанні. Рекомендується архітектура на основі канонічних layout (list-detail, supporting pane) з реактивною підпискою на зміни розміру вікна без перезапуску Activity.

Чи потрібно створювати окремі storyboards для iPad?

Ні. Використовуйте один storyboard з Size Classes варіаціями constraint або програмний layout через UIKit. SwiftUI взагалі не потребує storyboards. Окремі storyboards для iPad створюють дублювання та ускладнюють підтримку — краще адаптувати один макет через Size Classes.

Що таке canonical layouts в Material 3?

Canonical layouts — готові патерни адаптивної верстки від Google: list-detail (список + деталізація), supporting pane (контент + панель дій), feed (картки). Кожен патерн підтримує три стани (compact/medium/expanded) і реалізований у бібліотеці Material 3 Adaptive через ListDetailScaffold та SupportingPaneScaffold.

Підсумки

  • Adaptive Layout — дискретне перемикання макетів у точках breakpoint для різних пристроїв
  • Size Classes iOS (Compact/Regular) визначають layout для iPhone, iPad та Split View
  • UITraitCollection — механізм передачі характеристик середовища через ієрархію view
  • sw600dp — Android-кваліфікатор мінімальної ширини для планшетів 7"+
  • w600dp — кваліфікатор доступної ширини, що спрацьовує при повороті в ландшафт
  • Jetpack WindowManager надає WindowSizeClass (WindowWidthSizeClass: COMPACT/MEDIUM/EXPANDED)
  • Material 3 Adaptive — готові компоненти (ListDetailScaffold) з автоматичною адаптацією

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також