Adaptive Layout: Key Concepts, Size Classes and Qualifiers

Author: IT Sectr Published: 2026-02-26 Reading time: 10 min

Adaptive Layout is an approach to interface layout where the application uses different layouts for different device types and screen orientations. Unlike responsive design, adaptive layout switches between pre-built layouts at breakpoints. This article covers Size Classes and UITraitCollection in iOS, sw600dp/layout-w600dp qualifiers in Android, and provides practical examples.

Key Takeaways

  • Adaptive Layout — discrete layout switching depending on screen size, orientation, and device type
  • Size Classes in iOS classify space as Compact or Regular horizontally and vertically
  • UITraitCollection — a container of iOS environment characteristics (size, scale, force touch, color gamut)
  • sw600dp — Android minimum width qualifier for tablets (7" and larger)
  • layout-w600dp — available width qualifier that activates when sufficient space is available in the current orientation

What is Adaptive Layout?

Adaptive Layout is a UI strategy where the developer creates separate layouts for each device type and switches between them based on screen characteristics. Unlike responsive design where elements flow smoothly, adaptive layout uses discrete switching: phone — one layout, tablet — another, desktop — a third.

Apple introduced Size Classes in iOS 8 (2014) alongside universal binaries (apps running on both iPhone and iPad). Google introduced the smallestWidth qualifier (swdp) in Android 3.2 (2011). Both mechanisms solve the same problem: give developers a tool to create different UIs for different screens without code duplication.

Modern Apple HIG and Google Material Design guidelines agree that adaptive layout is essential for apps supporting more than one screen size. Google Play marks the lack of tablet adaptation as a drawback. The App Store has no formal requirement, but iPad users expect a native experience, not a stretched iPhone app.

iOS Size Classes and UITraitCollection

Size Classes are an iOS characteristic that defines available space as Compact or Regular on each axis. UITraitCollection is an iOS system for passing environment characteristics through the view hierarchy: size, screen scale, force touch capability, color gamut (light/dark), accessibility settings.

Device / OrientationHorizontalVertical
iPhone portrait (all models)CompactRegular
iPhone Plus/Pro Max landscapeRegularCompact
iPhone SE / mini landscapeCompactCompact
iPad portraitRegularRegular
iPad landscapeRegularRegular
iPad Split View (1/3 screen)CompactRegular

UITraitCollection is formed by the system and passed from UIApplication through UIWindow to each UIView. When orientation or window size changes (Split View), the system generates a new UITraitCollection and calls traitCollectionDidChange. In SwiftUI, changes are tracked through Environment Values and automatically redraw the view.

Adaptive Layout Example in Swift

Adaptation via UITraitCollection in UIKit is implemented by subscribing to trait collection changes and rebuilding the layout. The example below shows a controller that switches between a vertical stack for iPhone and a split layout for iPad.

swift
// Adaptive Controller with 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
    }
}

The traitCollectionDidChange method is called on any trait change, but we only check horizontalSizeClass — the most significant indicator for layout switching. In iOS 17+ it is recommended to use UIViewController.horizontalSizeClass as a computed property for reactive updates without subscribing to changes.

Android Qualifiers: sw600dp and w600dp

Android provides two main width qualifiers: swdp (smallestWidth) and wdp (available width). The difference is critical: sw is the minimum screen width regardless of orientation, w is the available width considering the current orientation. sw guarantees the layout loads for a device that physically has the specified minimum width. w responds to rotation — in landscape, w600dp may activate even if the portrait width is 360 dp.

QualifierConditionExample Device
layout-sw600dpMinimum width ≥ 600 dpiPad (768 dp), Pixel C (900 dp)
layout-w600dpCurrent width ≥ 600 dpiPad in Split View (600 dp), phone in landscape
layout-landLandscape orientationAny device rotated horizontally
layout-sw720dpMinimum width ≥ 720 dpiPad Pro (833 dp), Galaxy Tab S9 (800 dp)

Android resource hierarchy: res/layout/activity_main.xml (phone), res/layout-sw600dp/activity_main.xml (7" tablet), res/layout-w600dp-land/activity_main.xml (phone in landscape). Android selects the most specific qualifier matching the current device configuration. When no matching file exists, the base resource from res/layout/ is used.

Adaptive Layout Example in Kotlin

Modern Android development uses Jetpack WindowManager to determine window size instead of directly reading resources. This is especially important for foldable devices where screen size changes dynamically (Galaxy Fold unfolds from 6.2" to 7.6").

kotlin
// Adaptation via 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("Phone", 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()
    }
}

The currentWindowAdaptiveInfo() component from the androidx.window library adapts the interface for any device: phone, tablet, foldable, ChromeOS. Width classes: COMPACT (0–600 dp), MEDIUM (600–840 dp), EXPANDED (>840 dp). Height classes: COMPACT (0–480 dp), MEDIUM (480–900 dp), EXPANDED (>900 dp).

Adaptive Layout in SwiftUI

SwiftUI provides built-in tools for adaptive layout without directly using Size Classes. AnyLayout, ViewThatFits, and GeometryReader allow building interfaces that automatically adjust to available space.

swift
// SwiftUI adaptation via AnyLayout and 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("Favorites", systemImage: "star")
            Label("Recent", systemImage: "clock")
            Label("Settings", systemImage: "gear")
        }
        .padding()
    }
}

// ViewThatFits — automatic layout selection
struct SmartLayout: View {
    var body: some View {
        ViewThatFits {
            HStack { // Priority 1: horizontal
                CardView()
                CardView()
            }
            VStack { // If it doesn't fit — vertical
                CardView()
                CardView()
            }
        }
    }
}

ViewThatFits is a powerful SwiftUI tool that automatically selects the first child view that fits in the available space without clipping. This eliminates the need to explicitly check Size Classes for simple adaptive switches. AnyLayout allows switching layout type (HStack/VStack) without if-else in the view body.

Adaptive Layout in Jetpack Compose

Jetpack Compose uses WindowSizeClass (from the Material 3 adaptive library) and BoxWithConstraints for adaptive layout. Unlike XML qualifiers, Compose determines window size at runtime and reactively recomposes the UI on configuration changes.

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

        if (width >= 600.dp) {
            // Tablet: two columns (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 {
            // Phone: one column with 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 provide ready-made components: ListDetailScaffold, SupportingPaneScaffold, NavigationSuiteScaffold. These components automatically adapt to WindowWidthSizeClass — switching between stacked (COMPACT) and side-by-side (MEDIUM/EXPANDED). The developer just needs to choose a pattern and pass content panels.

Frequently Asked Questions

What is the difference between sw600dp and w600dp?

sw600dp (smallestWidth) — minimum screen width regardless of orientation. Activates on 7"+ tablets always. w600dp (available width) — available width considering the current orientation. Activates on phones in landscape where width may exceed 600 dp.

What is UITraitCollection in iOS?

UITraitCollection is an iOS object containing environment characteristics: size classes (horizontal/vertical), display scale, force touch capability, user interface idiom (iPhone/iPad), color gamut, accessibility settings. It is passed from UIApplication through the view hierarchy and changes when orientation or window size changes.

How to adapt an app for foldable devices?

Use Jetpack WindowManager (Android) and UIScreen nativeBounds + traitCollection (iOS). Foldable devices change Size Class when unfolding. An architecture based on canonical layouts (list-detail, supporting pane) with reactive subscription to window size changes without Activity restart is recommended.

Do I need to create separate storyboards for iPad?

No. Use a single storyboard with Size Class constraint variations or programmatic layout via UIKit. SwiftUI does not require storyboards at all. Separate storyboards for iPad create duplication and complicate maintenance — it is better to adapt one layout through Size Classes.

What are canonical layouts in Material 3?

Canonical layouts are ready-made adaptive layout patterns from Google: list-detail (list + detail), supporting pane (content + action panel), feed (cards). Each pattern supports three states (compact/medium/expanded) and is implemented in the Material 3 Adaptive library through ListDetailScaffold and SupportingPaneScaffold.

Summary

  • Adaptive Layout — discrete layout switching at breakpoints for different devices
  • Size Classes iOS (Compact/Regular) define layout for iPhone, iPad, and Split View
  • UITraitCollection — mechanism for passing environment characteristics through the view hierarchy
  • sw600dp — Android minimum width qualifier for 7"+ tablets
  • w600dp — available width qualifier that activates on landscape rotation
  • Jetpack WindowManager provides WindowSizeClass (WindowWidthSizeClass: COMPACT/MEDIUM/EXPANDED)
  • Material 3 Adaptive — ready-made components (ListDetailScaffold) with automatic adaptation

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also