Adaptive Layout — pendekatan dalam merancang antarmuka di mana aplikasi menggunakan tata letak yang berbeda untuk berbagai jenis perangkat dan orientasi layar. Berbeda dengan responsive design, adaptive layout beralih di antara tata letak yang sudah disiapkan sebelumnya di titik-titik breakpoint. Artikel ini membahas Size Classes dan UITraitCollection di iOS, kualifikator sw600dp/layout-w600dp di Android, dan memberikan contoh praktis.
Poin Utama
Adaptive Layout — strategi membangun antarmuka di mana pengembang membuat tata letak terpisah untuk setiap jenis perangkat dan beralih di antara mereka berdasarkan karakteristik layar. Berbeda dengan responsive design, di mana elemen mengalir secara mulus, adaptive layout menggunakan perpindahan diskrit: telepon — satu tata letak, tablet — lainnya, desktop — ketiga.
Apple memperkenalkan Size Classes di iOS 8 (2014) bersama dengan aplikasi universal (universal binaries yang berfungsi di iPhone dan iPad). Google memperkenalkan kualifikator smallestWidth (sw
Rekomendasi modern Apple HIG dan Google Material Design sepakat bahwa adaptive layout wajib untuk aplikasi yang mendukung lebih dari satu ukuran layar. Google Play menandai kurangnya adaptasi untuk tablet sebagai kekurangan. App Store tidak memiliki persyaratan formal, tetapi pengguna iPad mengharapkan pengalaman native, bukan aplikasi iPhone yang diregangkan.
Size Classes — fitur iOS yang mendefinisikan ruang yang tersedia sebagai Compact atau Regular di setiap sumbu. UITraitCollection — sistem iOS untuk mentransmisikan karakteristik lingkungan melalui hierarki view: ukuran, skala layar, force touch capability, gamut warna (light/dark), accessibility settings.
| Perangkat / Orientasi | Horizontal | Vertical |
|---|---|---|
| iPhone potret (semua model) | Compact | Regular |
| iPhone Plus/Pro Max lanskap | Regular | Compact |
| iPhone SE / mini lanskap | Compact | Compact |
| iPad potret | Regular | Regular |
| iPad lanskap | Regular | Regular |
| iPad Split View (1/3 layar) | Compact | Regular |
UITraitCollection dibentuk oleh sistem dan ditransmisikan dari UIApplication melalui UIWindow ke setiap UIView. Saat orientasi atau ukuran jendela berubah (Split View), sistem menghasilkan UITraitCollection baru dan memanggil traitCollectionDidChange. Di SwiftUI, perubahan dilacak melalui Environment Values dan secara otomatis menggambar ulang view.
Adaptasi melalui UITraitCollection di UIKit diimplementasikan dengan berlangganan perubahan trait collection dan membangun ulang tata letak. Contoh di bawah menunjukkan sebuah controller yang beralih antara stack vertikal untuk iPhone dan tata letak split untuk iPad.
// Kontroler adaptif dengan 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 + konten
} else {
showCompactLayout() // iPhone: bilah tab + 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
}
}Metode traitCollectionDidChange dipanggil pada setiap perubahan traits, tetapi kita hanya memeriksa horizontalSizeClass — indikator paling signifikan untuk perubahan tata letak. Di iOS 17+ disarankan menggunakan UIViewController.horizontalSizeClass sebagai computed property untuk pembaruan reaktif tanpa berlangganan perubahan.
Android menyediakan dua kualifikator lebar utama: sw
| Kualifikator | Kondisi | Contoh perangkat |
|---|---|---|
| layout-sw600dp | Lebar minimum ≥ 600 dp | iPad (768 dp), Pixel C (900 dp) |
| layout-w600dp | Lebar saat ini ≥ 600 dp | iPad di Split View (600 dp), telepon dalam lanskap |
| layout-land | Orientasi lanskap | Perangkat apa pun diputar horizontal |
| layout-sw720dp | Lebar minimum ≥ 720 dp | iPad Pro (833 dp), Galaxy Tab S9 (800 dp) |
Hierarki sumber daya di Android: res/layout/activity_main.xml (telepon), res/layout-sw600dp/activity_main.xml (tablet 7"), res/layout-w600dp-land/activity_main.xml (telepon dalam lanskap). Android memilih kualifikator paling spesifik yang sesuai dengan konfigurasi perangkat saat ini. Jika tidak ada file yang cocok, sumber daya dasar dari res/layout/ digunakan.
Pengembangan Android modern menggunakan Jetpack WindowManager untuk menentukan ukuran jendela daripada membaca sumber daya secara langsung. Ini sangat penting untuk perangkat lipat, di mana ukuran layar dapat berubah secara dinamis (Galaxy Fold terbuka dari 6,2" menjadi 7,6").
// Adaptasi melalui 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("Telepon", 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()
}
}Komponen currentWindowAdaptiveInfo() dari pustaka androidx.window menyesuaikan antarmuka dengan perangkat apa pun: telepon, tablet, lipat, ChromeOS. Kelas lebar: COMPACT (0–600 dp), MEDIUM (600–840 dp), EXPANDED (>840 dp). Kelas tinggi: COMPACT (0–480 dp), MEDIUM (480–900 dp), EXPANDED (>900 dp).
SwiftUI menyediakan alat bawaan untuk tata letak adaptif tanpa penggunaan langsung Size Classes. AnyLayout, ViewThatFits, dan GeometryReader memungkinkan membangun antarmuka yang secara otomatis menyesuaikan dengan ruang yang tersedia.
// Adaptasi SwiftUI melalui AnyLayout dan 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("Favorit", systemImage: "star")
Label("Terbaru", systemImage: "clock")
Label("Pengaturan", systemImage: "gear")
}
.padding()
}
}
// ViewThatFits — pemilihan tata letak otomatis
struct SmartLayout: View {
var body: some View {
ViewThatFits {
HStack { // Prioritas 1: horizontal
CardView()
CardView()
}
VStack { // Jika tidak muat — vertikal
CardView()
CardView()
}
}
}
}ViewThatFits — alat SwiftUI yang kuat yang secara otomatis memilih view anak pertama yang muat di ruang yang tersedia tanpa terpotong. Ini menghilangkan kebutuhan untuk memeriksa Size Classes secara eksplisit untuk perpindahan adaptif sederhana. AnyLayout memungkinkan mengubah tipe tata letak (HStack/VStack) tanpa if-else di dalam body view.
Jetpack Compose menggunakan WindowSizeClass (dari pustaka Material 3 adaptive) dan BoxWithConstraints untuk tata letak adaptif. Berbeda dengan kualifikator XML, Compose menentukan ukuran jendela saat runtime dan menyusun ulang UI secara reaktif saat konfigurasi berubah.
// Adaptasi Jetpack Compose melalui BoxWithConstraints
@Composable
fun AdaptiveList(items: List<String>) {
BoxWithConstraints {
val width = maxWidth
if (width >= 600.dp) {
// Tablet: dua kolom (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 {
// Telepon: satu kolom dengan navigasi
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 menyediakan komponen siap pakai: ListDetailScaffold, SupportingPaneScaffold, NavigationSuiteScaffold. Komponen-komponen ini secara otomatis menyesuaikan dengan WindowWidthSizeClass — beralih antara stacked (COMPACT) dan side-by-side (MEDIUM/EXPANDED). Pengembang hanya perlu memilih pola dan memberikan panel konten.
Pertanyaan yang Sering Diajukan
sw600dp (smallestWidth) — lebar minimum layar terlepas dari orientasi. Selalu aktif di tablet 7"+. w600dp (available width) — lebar tersedia dengan mempertimbangkan orientasi saat ini. Aktif di telepon dalam lanskap, di mana lebar dapat melebihi 600 dp.
UITraitCollection — objek iOS yang berisi karakteristik lingkungan: size classes (horizontal/vertical), display scale, force touch capability, user interface idiom (iPhone/iPad), color gamut, accessibility settings. Ditransmisikan dari UIApplication melalui hierarki view dan berubah saat orientasi atau ukuran jendela berubah.
Gunakan Jetpack WindowManager (Android) dan UIScreen nativeBounds + traitCollection (iOS). Perangkat lipat mengubah Size Class saat dibuka. Arsitektur berdasarkan tata letak kanonis (list-detail, supporting pane) dengan langganan reaktif terhadap perubahan ukuran jendela tanpa memulai ulang Activity direkomendasikan.
Tidak. Gunakan satu storyboard dengan variasi Size Classes constraint atau tata letak programatik melalui UIKit. SwiftUI sama sekali tidak memerlukan storyboards. Storyboards terpisah untuk iPad menciptakan duplikasi dan mempersulit pemeliharaan — lebih baik menyesuaikan satu tata letak melalui Size Classes.
Canonical layouts — pola tata letak adaptif siap pakai dari Google: list-detail (daftar + detail), supporting pane (konten + panel tindakan), feed (kartu). Setiap pola mendukung tiga keadaan (compact/medium/expanded) dan diimplementasikan di pustaka Material 3 Adaptive melalui ListDetailScaffold dan SupportingPaneScaffold.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga