Adaptive Layout, uygulamanın farklı cihaz türleri ve ekran yönleri için farklı düzenler kullandığı bir arayüz düzeni yaklaşımıdır. Responsive design'ın aksine, adaptive layout kesme noktalarında önceden hazırlanmış düzenler arasında geçiş yapar. Bu makale iOS'ta Size Classes ve UITraitCollection'ı, Android'de sw600dp/layout-w600dp niteleyicilerini ve pratik örnekleri ele almaktadır.
Önemli Noktalar
Adaptive Layout, geliştiricinin her cihaz türü için ayrı düzenler oluşturduğu ve ekran özelliklerine göre aralarında geçiş yaptığı bir UI stratejisidir. Öğelerin sorunsuzca aktığı responsive design'ın aksine, adaptive layout ayrık geçiş kullanır: telefon — bir düzen, tablet — başka bir düzen, masaüstü — üçüncü bir düzen.
Apple, iOS 8'de (2014) evrensel uygulamalarla (hem iPhone hem iPad'de çalışan ikili dosyalar) birlikte Size Classes'ı tanıttı. Google, Android 3.2'de (2011) smallestWidth niteleyicisini (sw
Modern Apple HIG ve Google Material Design yönergeleri, birden fazla ekran boyutunu destekleyen uygulamalar için adaptive layout'un gerekli olduğu konusunda hemfikirdir. Google Play, tablet uyarlamasının olmamasını bir eksiklik olarak işaretler. App Store'un resmi bir gereksinimi yoktur, ancak iPad kullanıcıları gerilmiş bir iPhone uygulaması değil, yerel bir deneyim bekler.
Size Classes, her eksende kullanılabilir alanı Compact (Dar) veya Regular (Normal) olarak tanımlayan bir iOS özelliğidir. UITraitCollection, görünüm hiyerarşisi boyunca ortam özelliklerini ileten bir iOS sistemidir: boyut, ekran ölçeği, force touch yeteneği, renk gamı (açık/koyu), erişilebilirlik ayarları.
| Cihaz / Yön | Yatay | Dikey |
|---|---|---|
| iPhone dikey (tüm modeller) | Compact | Regular |
| iPhone Plus/Pro Max yatay | Regular | Compact |
| iPhone SE / mini yatay | Compact | Compact |
| iPad dikey | Regular | Regular |
| iPad yatay | Regular | Regular |
| iPad Split View (1/3 ekran) | Compact | Regular |
UITraitCollection sistem tarafından oluşturulur ve UIApplication'dan UIWindow aracılığıyla her UIView'e iletilir. Yön veya pencere boyutu (Split View) değiştiğinde, sistem yeni bir UITraitCollection oluşturur ve traitCollectionDidChange'i çağırır. SwiftUI'da değişiklikler Environment Values aracılığıyla izlenir ve görünüm otomatik olarak yeniden çizilir.
UIKit'te UITraitCollection aracılığıyla uyarlama, trait collection değişikliklerine abone olarak ve düzeni yeniden oluşturarak uygulanır. Aşağıdaki örnek, iPhone için dikey yığın ile iPad için bölünmüş düzen arasında geçiş yapan bir denetleyiciyi göstermektedir.
// Size Classes ile Uyarlanabilir Denetleyici
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: kenar çubuğu + içerik
} else {
showCompactLayout() // iPhone: sekme çubuğu + 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 yöntemi herhangi bir özellik değişikliğinde çağrılır, ancak yalnızca düzen değişikliği için en önemli gösterge olan horizontalSizeClass'ı kontrol ederiz. iOS 17+'da, değişikliklere abone olmadan reaktif güncellemeler için UIViewController.horizontalSizeClass'ın hesaplanan özellik olarak kullanılması önerilir.
Android iki ana genişlik niteleyicisi sağlar: sw
| Niteleyici | Koşul | Cihaz Örneği |
|---|---|---|
| layout-sw600dp | Minimum genişlik ≥ 600 dp | iPad (768 dp), Pixel C (900 dp) |
| layout-w600dp | Mevcut genişlik ≥ 600 dp | Split View'da iPad (600 dp), yatay modda telefon |
| layout-land | Yatay yön | Yatay döndürülmüş herhangi bir cihaz |
| layout-sw720dp | Minimum genişlik ≥ 720 dp | iPad Pro (833 dp), Galaxy Tab S9 (800 dp) |
Android'de kaynak hiyerarşisi: res/layout/activity_main.xml (telefon), res/layout-sw600dp/activity_main.xml (7\" tablet), res/layout-w600dp-land/activity_main.xml (yatay modda telefon). Android, mevcut cihaz yapılandırmasına uyan en spesifik niteleyiciyi seçer. Eşleşen dosya olmadığında, res/layout/ dizinindeki temel kaynak kullanılır.
Modern Android geliştirme, kaynakları doğrudan okumak yerine pencere boyutunu belirlemek için Jetpack WindowManager kullanır. Bu, ekran boyutunun dinamik olarak değiştiği katlanabilir cihazlar için özellikle önemlidir (Galaxy Fold 6.2\"'den 7.6\"'ya açılır).
// Jetpack WindowManager ile Uyarlama
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()
}
}androidx.window kütüphanesindeki currentWindowAdaptiveInfo() bileşeni, arayüzü herhangi bir cihaza uyarlar: telefon, tablet, katlanabilir, ChromeOS. Genişlik sınıfları: COMPACT (0–600 dp), MEDIUM (600–840 dp), EXPANDED (>840 dp). Yükseklik sınıfları: COMPACT (0–480 dp), MEDIUM (480–900 dp), EXPANDED (>900 dp).
SwiftUI, Size Classes'ı doğrudan kullanmadan uyarlanabilir düzen için yerleşik araçlar sağlar. AnyLayout, ViewThatFits ve GeometryReader, mevcut alana otomatik olarak uyum sağlayan arayüzler oluşturmayı sağlar.
// AnyLayout ve ViewThatFits ile SwiftUI Uyarlaması
import SwiftUI
struct AdaptiveGrid: View {
@Environment(\.horizontalSizeClass) private var hSizeClass
var body: some View {
let layout = hSizeClass == .regular
? AnyLayout(HStackLayout())
: AnyLayout(VStackLayout())
layout {
Label("Favoriler", systemImage: "star")
Label("Son", systemImage: "clock")
Label("Ayarlar", systemImage: "gear")
}
.padding()
}
}
// ViewThatFits — otomatik düzen seçimi
struct SmartLayout: View {
var body: some View {
ViewThatFits {
HStack { // Öncelik 1: yatay
CardView()
CardView()
}
VStack { // Sığmazsa — dikey
CardView()
CardView()
}
}
}
}ViewThatFits, kırpma olmadan mevcut alana sığan ilk alt görünümü otomatik olarak seçen güçlü bir SwiftUI aracıdır. Bu, basit uyarlanabilir geçişler için Size Classes'ı açıkça kontrol etme ihtiyacını ortadan kaldırır. AnyLayout, görünüm gövdesinde if-else olmadan düzen türünü (HStack/VStack) değiştirmeyi sağlar.
Jetpack Compose, uyarlanabilir düzen için WindowSizeClass'ı (Material 3 adaptive kütüphanesinden) ve BoxWithConstraints'ı kullanır. XML niteleyicilerinin aksine, Compose pencere boyutunu çalışma zamanında belirler ve yapılandırma değişikliklerinde UI'ı reaktif olarak yeniden oluşturur.
// BoxWithConstraints ile Jetpack Compose uyarlanabilir
@Composable
fun AdaptiveList(items: List<String>) {
BoxWithConstraints {
val width = maxWidth
if (width >= 600.dp) {
// Tablet: iki sütun (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: gezinmeli tek sütun
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, hazır bileşenler sağlar: ListDetailScaffold, SupportingPaneScaffold, NavigationSuiteScaffold. Bu bileşenler WindowWidthSizeClass'a otomatik olarak uyum sağlar — yığılmış (COMPACT) ve yan yana (MEDIUM/EXPANDED) arasında geçiş yapar. Geliştiricinin sadece bir desen seçmesi ve içerik panellerini iletmesi yeterlidir.
Sıkça Sorulan Sorular
sw600dp (smallestWidth) — yönden bağımsız minimum ekran genişliği. 7\"+ tabletlerde her zaman etkinleşir. w600dp (available width) — mevcut yönü dikkate alan kullanılabilir genişlik. Genişliğin 600 dp'yi aşabileceği yatay moddaki telefonlarda etkinleşir.
UITraitCollection, ortam özelliklerini içeren bir iOS nesnesidir: size classes (yatay/dikey), ekran ölçeği, force touch yeteneği, kullanıcı arayüzü idiomu (iPhone/iPad), renk gamı, erişilebilirlik ayarları. UIApplication'dan görünüm hiyerarşisi boyunca iletilir ve yön veya pencere boyutu değiştiğinde değişir.
Jetpack WindowManager (Android) ve UIScreen nativeBounds + traitCollection (iOS) kullanın. Katlanabilir cihazlar açıldığında Size Class'ı değiştirir. Activity'yi yeniden başlatmadan pencere boyutu değişikliklerine reaktif abonelik ile kurallı düzenlere (list-detail, supporting pane) dayalı bir mimari önerilir.
Hayır. Size Class kısıtlama varyasyonlarına sahip tek bir storyboard veya UIKit aracılığıyla programlı düzen kullanın. SwiftUI hiç storyboard gerektirmez. iPad için ayrı storyboard'lar çoğaltma yaratır ve bakımı karmaşıklaştırır — Size Classes aracılığıyla tek bir düzeni uyarlamak daha iyidir.
Canonical layouts, Google'ın hazır uyarlanabilir düzen desenleridir: list-detail (liste + detay), supporting pane (içerik + eylem paneli), feed (kartlar). Her desen üç durumu (compact/medium/expanded) destekler ve Material 3 Adaptive kütüphanesinde ListDetailScaffold ve SupportingPaneScaffold aracılığıyla uygulanmıştır.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun