Agar kolom label dan status di jendela Mac tetap stabil sementara deskripsinya meluas, tetapkan lebar eksplisit untuk kolom luar dan gunakan bingkai fleksibel untuk deskripsi. Gunakan prioritas tata letak saat subview bersaing, dan pertahankan ukuran ideal hanya pada sumbu yang diperlukan.
Tutorial ini membuat jendela kecil yang ukurannya dapat diubah, berisi label dan deskripsi item sintetis. Tujuannya adalah menjaga kolom tetap sejajar sekaligus memungkinkan deskripsi yang lebih panjang membungkus saat jendela dipersempit. Semuanya merupakan bagian dari aplikasi contoh; tata letaknya menggunakan tampilan SwiftUI biasa.
Kontrol ukuran apa yang sebaiknya digunakan setiap subview?
Tata letak SwiftUI dimulai ketika induk mengusulkan ukuran. Setiap anak merespons dengan ukuran yang diinginkan, lalu kontainer menata hasilnya. Titik awal yang berguna adalah menentukan dimensi mana yang boleh berubah untuk setiap anak. Apple menjelaskan proses pengusulan dan respons ini dalam Panduan tata letak di SwiftUI.
Untuk jendela ini, tetapkan tiga peran: kolom label dengan lebar pilihan, kolom deskripsi yang menyerap perubahan lebar, dan kolom status dengan lebar pilihan. Bedakan lebar tetap numerik ini dari ukuran ideal sebuah tampilan: keduanya menyatakan persyaratan yang berbeda.
| Persyaratan | Kontrol | Dampak praktis |
|---|---|---|
| Menyediakan lebar kolom tertentu | .frame(width: 96) |
Bingkai yang dihasilkan memiliki lebar tersebut. |
| Membiarkan deskripsi menyerap lebar yang tersedia | .frame(minWidth: 160, maxWidth: .infinity, alignment: .leading) |
Bingkai menerima lebar yang diusulkan dengan tunduk pada nilai minimumnya. |
| Mempertahankan ukuran ideal konten pada sumbu tertentu | .fixedSize(horizontal: false, vertical: true) |
Lebar dapat menyesuaikan tata letak sementara tinggi mempertahankan ukuran idealnya. |
| Mengutamakan satu subview saat pembagian ruang | .layoutPriority(1) |
Induk mengutamakan anak tersebut dibandingkan anak dengan prioritas lebih rendah. |
Apple mendokumentasikan dimensi numerik dalam pengubah bingkai tetap, batas minimum dan maksimum dalam pengubah bingkai fleksibel, ukuran ideal per sumbu dalam fixedSize(horizontal:vertical:), dan preferensi antar-subview dalam layoutPriority(_:).
Batas maksimum tak hingga berarti tampilan diizinkan meluas dalam batas ukuran yang diusulkan; ini tidak meminta jendela dengan lebar tak terhingga. Dengan memberikan batas minimum dan maksimum, bingkai akan menggunakan dimensi yang diusulkan, dibatasi oleh nilai-nilai tersebut. Parameter perataan memosisikan anak di dalam bingkai itu. Detail ini penting saat teks itu sendiri lebih sempit daripada ruang yang dialokasikan untuk kolomnya. Dokumentasi bingkai fleksibel Apple
Buat contoh macOS lengkap yang ukurannya dapat diubah
Buat proyek aplikasi macOS di Xcode menggunakan SwiftUI dan tetapkan target deployment ke macOS 13 atau lebih baru. Ganti deklarasi app dan content view yang dihasilkan dengan satu file ini, dan sisakan hanya satu titik masuk aplikasi. Contoh ini menggunakan kontrol resizability jendela macOS 13. Dokumentasi WindowResizability Apple
Berikut contoh aplikasi lengkap. Contoh ini menyertakan string sampel dan tombol alih untuk beralih antara deskripsi pendek dan panjang. Aplikasi SwiftUI ini tidak dapat dikompilasi di lingkungan riset karena toolchain Swift tidak tersedia; periksa perilakunya di Xcode sebelum mengintegrasikannya ke proyek yang sudah ada.
import SwiftUI
@main
struct ColumnAllocationApp: App {
var body: some Scene {
WindowGroup {
AllocationView()
.frame(minWidth: 420, minHeight: 260)
}
.windowResizability(.contentMinSize)
}
}
struct AllocationView: View {
@State private var showLongDescriptions = true
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Item overview")
.font(.title2)
Toggle(
"Show longer descriptions",
isOn: $showLongDescriptions
)
ScrollView {
VStack(alignment: .leading, spacing: 16) {
AllocationRow(
label: "Item A",
summary: showLongDescriptions
? "A blue folder with several sample notes and a longer descriptive label."
: "Blue folder",
status: "Ready"
)
AllocationRow(
label: "Item B",
summary: showLongDescriptions
? "A green notebook containing a short list of synthetic example entries."
: "Green notebook",
status: "Draft"
)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.padding(16)
}
}
struct AllocationRow: View {
let label: String
let summary: String
let status: String
var body: some View {
HStack(alignment: .top, spacing: 12) {
Text(label)
.frame(width: 96, alignment: .leading)
Text(summary)
.fixedSize(horizontal: false, vertical: true)
.frame(
minWidth: 160,
maxWidth: .infinity,
alignment: .leading
)
Text(status)
.fixedSize(horizontal: true, vertical: false)
.frame(width: 72, alignment: .trailing)
}
}
}
Kolom deskripsi adalah satu-satunya kolom yang fleksibel secara horizontal. Pemanggilan fixedSize membuat ukuran horizontal tetap responsif sekaligus mempertahankan ruang vertikal yang dibutuhkan teks yang membungkus. Bingkai fleksibelnya kemudian mengisi kolom yang dialokasikan. Status mempertahankan lebar teks idealnya di dalam bingkai terpisah selebar 72 poin. Apple mengingatkan bahwa konten berukuran ideal dapat melampaui batas induknya, sehingga perlakuan status ini mengasumsikan nilai pendek yang diberikan, Ready dan Draft. Dokumentasi fixedSize Apple
Baris-barisnya tidak memiliki tinggi tetap. Ini disengaja: deskripsi yang lebih sempit mungkin memerlukan lebih banyak baris. Tampilan gulir menyediakan cara untuk menjangkau baris ketika tinggi gabungannya melebihi area yang tersedia. Hindari menambahkan tinggi baris yang membatasi hanya agar susunan awal tampak seragam.
Jendela menggunakan .contentMinSize, yang menentukan dimensi minimum berdasarkan konten dan tidak menetapkan ukuran maksimum. Di sini, lebar minimum 420 poin pada tampilan akar menetapkan batas bawah praktis untuk contoh ini. Kontrol ini berlaku untuk jendela yang dibuat oleh scene aplikasi. Dokumentasi contentMinSize Apple
Deklarasi yang mengembalikan some View menggunakan tipe hasil opak Swift: tipe konkretnya tetap tersembunyi, sementara konformansinya terhadap View tetap tersedia. Anda dapat menyesuaikan rangkaian pengubah tanpa menuliskan tipe pengembalian bertingkatnya. Bahasa Pemrograman Swift: Tipe Protokol Opaque dan Boxed
Hitung anggaran lebar sebelum mengubah prioritas
Anggaran lebar memperjelas batas tata letak. Dalam contoh ini, kurangi padding horizontal, kedua jarak, dan dua kolom tetap dari lebar konten jendela. Sisanya menjadi lebar deskripsi. Ini adalah dimensi desain ilustratif, bukan ukuran kontrol native.
Lebar deskripsi
= lebar konten - padding horizontal - jarak - kolom tetap
= lebar konten - (2 × 16) - (2 × 12) - 96 - 72
= lebar konten - 224
Pada 640 poin: tersisa 416 poin untuk deskripsi.
Pada 420 poin: tersisa 196 poin untuk deskripsi.
Anggaran minimum baris:
32 + 24 + 96 + 160 + 72 = 384 poin.
Perhitungannya diperiksa dengan kalkulasi lokal deterministik. Perhitungan ini menggambarkan baris tersebut saat menerima lebar konten dikurangi padding yang disebutkan; dekorasi jendela tidak disertakan. Batas minimum jendela 420 poin yang dipilih menyisakan ruang di atas minimum 384 poin yang dihitung untuk baris. Jika kontainer di sekelilingnya menambahkan inset, masukkan juga nilai itu ke dalam anggaran.
Perhitungan ini juga menunjukkan permintaan yang mustahil dipenuhi. Di bawah anggaran minimum gabungan, ketiga kolom tidak dapat mempertahankan lebar dan jaraknya secara bersamaan. Pilih lebar minimum jendela yang lebih besar, dimensi minimum yang lebih kecil, atau susunan lain. Menaikkan prioritas tidak dapat menciptakan ruang tambahan.
Kapan prioritas tata letak membantu?
Prioritas berguna ketika dua subview sama-sama dapat menyusut dan Anda ingin salah satunya mempertahankan lebih banyak ruang. Misalnya, judul deskriptif mungkin lebih layak diutamakan daripada anotasi sekunder. Pasang .layoutPriority(1) pada anak yang ikut serta dalam keputusan alokasi oleh induk tersebut. Apple menyatakan bahwa tampilan dengan prioritas lebih tinggi menyusut lebih belakangan dan meluas lebih dahulu, dengan ruang tetap disediakan untuk kebutuhan minimum anak berprioritas lebih rendah. Dokumentasi layoutPriority Apple
Contoh lengkap ini tidak memerlukan pengubah prioritas: kebijakan alokasinya sudah dinyatakan melalui dua kolom tetap dan satu kolom fleksibel. Nilai prioritas juga bukan bobot proporsional. Menetapkan prioritas 2 dan 1 tidak menghasilkan rasio lebar dua banding satu; tujuan yang didokumentasikan adalah memberikan preferensi saat alokasi. Dokumentasi layoutPriority Apple
Hindari menambahkan Spacer horizontal antara deskripsi dan status kecuali Anda menginginkan jarak fleksibel terpisah. Spacer meluas sepanjang sumbu utama stack-nya dan mendorong subview menjauh satu sama lain. Karena bingkai deskripsi sudah menyerap perubahan lebar, peserta tambahan itu tidak diperlukan di sini. Dokumentasi Spacer Apple
Apa yang sebaiknya terjadi saat teks tidak lagi muat?
Tentukan secara terpisah apakah setiap bidang teks perlu membungkus, dipotong, atau memerlukan susunan lain. Deskripsi dalam contoh dapat membungkus tanpa batas baris. Menerapkan .lineLimit(1) akan membatasi setiap elemen teks yang dikenai pengubah itu menjadi satu baris, sehingga persyaratannya berubah. Batas baris nil menghapus batas tersebut, tetapi tidak menghapus batasan dari bingkai yang membungkusnya. Dokumentasi lineLimit Apple
Untuk nilai status yang lebih panjang, pertimbangkan ulang kolom 72 poin. Hapus perlindungan ukuran ideal horizontal jika teks boleh membungkus, perbesar lebar kolom, atau pindahkan status ke bawah deskripsi. Mempertahankan .fixedSize(horizontal: true, vertical: false) saat bingkai yang membungkusnya dipersempit dapat membuat teks melampaui bingkai tersebut. Dokumentasi fixedSize Apple
Jika jendela yang ringkas sebaiknya menyusun ulang konten, macOS 13 dan versi lebih baru menyediakan ViewThatFits. Berikan susunan horizontal terlebih dahulu, lalu susunan bertumpuk. Tampilan ini memilih anak pertama yang ukuran idealnya muat pada sumbu yang dibatasi, jadi evaluasi ruang horizontal menggunakan ViewThatFits(in: .horizontal) jika lebar menjadi faktor penentu. Pastikan kandidat terakhir dapat digunakan pada lebar minimum yang didukung. Dokumentasi ViewThatFits Apple
Anggap ini sebagai kebijakan tata letak alternatif. Contoh saat ini sengaja mempertahankan kolom dan menetapkan lebar minimum jendela. Gunakan susunan bertumpuk sebagai fallback ketika mempertahankan susunan horizontal kurang penting daripada memungkinkan jendela yang lebih sempit.
Bagaimana pendekatan berubah untuk tampilan AppKit dan TextKit yang disematkan?
Jika deskripsi nantinya menjadi komponen teks AppKit, gunakan NSViewRepresentable untuk mengintegrasikan tampilan tersebut. Apple menyatakan secara eksplisit bahwa SwiftUI mengendalikan frame dan bounds dari tampilan yang diwakili; mengubah properti tersebut secara langsung dari kode Anda sendiri bertentangan dengan tata letak SwiftUI. Konfigurasikan konten melalui siklus hidup representable dan nyatakan ukuran luarnya melalui SwiftUI. Dokumentasi NSViewRepresentable Apple
Pada macOS 13 dan versi lebih baru, metode sizeThatFits(_:nsView:context:) milik representable dapat mengembalikan ukuran yang diinginkannya untuk suatu usulan ukuran. Metode ini dapat dipanggil beberapa kali dengan usulan berbeda dalam satu tahap tata letak. Mengembalikan nil meminta algoritme ukuran default. Pengukuran komponen yang membungkus teks harus memperhitungkan lebar yang diusulkan, alih-alih selalu mengembalikan lebar teks yang belum membungkus. Dokumentasi ukuran representable Apple
Bedakan dimensi yang tidak ditentukan dari nol: SwiftUI menggunakan usulan yang tidak ditentukan untuk meminta ukuran ideal, sedangkan nol dapat menguji ukuran minimum. Rutinitas pengukuran khusus harus menangani kasus-kasus ini dengan sengaja. Dokumentasi ProposedViewSize Apple
Di dalam tata letak AppKit, resistansi kompresi mengatur seberapa kuat sebuah NSView menolak untuk menjadi lebih kecil daripada ukuran intrinsiknya. Periksa pengaturan itu jika komponen yang membungkus teks menolak untuk dikompresi, bersama perilaku ukurannya di SwiftUI. Dokumentasi setContentCompressionResistancePriority Apple
Untuk NSTextView yang menggunakan TextKit 2, gunakan jalur modern textLayoutManager. Ringkasan TextKit terbaru dari Apple membedakannya dari layoutManager yang lama, dan panduan migrasinya menjelaskan bahwa mengakses properti lama dapat memicu peralihan kompatibilitas satu arah ke TextKit 1 untuk instance tersebut. Mengubah mesin teks tidak diperlukan untuk mengalokasikan kolom antarsubview. Ringkasan TextKit Apple, Yang baru di TextKit dan tampilan teks
Periksa hasil pada batas ukuran jendela
Jalankan contoh dan periksa kondisi berikut sebelum menerapkan pola ini pada antarmuka yang lebih besar:
- Perlebar jendela: kolom deskripsi seharusnya meluas sementara bingkai label dan status mempertahankan lebarnya.
- Persempit jendela hingga batas minimum yang didukung: deskripsi yang lebih panjang seharusnya membungkus dan menambah tinggi baris.
- Aktifkan dan nonaktifkan
Show longer descriptions: deskripsi pendek seharusnya tetap sejajar dengan kolom yang sama. - Ganti dengan status sintetis yang lebih panjang: tentukan apakah bidang itu perlu diperlebar, dibungkus, atau disusun ulang.
- Kurangi tinggi yang tersedia: pastikan semua baris tetap dapat dijangkau.
Untuk melihat lapisan mana yang menerima ruang, tambahkan sementara batas tepat sebelum dan sesudah bingkai fleksibel deskripsi. Batas dalam menunjukkan batas teks; batas luar menunjukkan kolom yang dialokasikan. Apple merekomendasikan batas berwarna sebagai cara praktis untuk mendiagnosis ukuran stack dan padding. Panduan tata letak di SwiftUI
Pertahankan kebijakan ukuran yang eksplisit saat antarmuka berkembang: sediakan lebar jika diperlukan untuk perataan, berikan area fleksibel yang terdefinisi untuk konten yang berubah-ubah, dan tentukan perilaku setiap bidang pada lebar minimum. Dengan kebijakan dan anggaran lebar tersebut, Anda dapat mengubah label atau menambahkan konten tanpa menebak-nebak pengubah mana yang perlu diberi prioritas lebih tinggi.
Sumber
- Panduan tata letak di SwiftUI — Apple
- frame(width:height:alignment:) — Apple
- frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) — Apple
- fixedSize(horizontal:vertical:) — Apple
- layoutPriority(_:) — Apple
- WindowResizability — Apple
- contentMinSize — Apple
- Tipe Protokol Opaque dan Boxed — Swift.org
- Spacer — Apple
- lineLimit(_:) — Apple
- ViewThatFits — Apple
- NSViewRepresentable — Apple
- sizeThatFits(_:nsView:context:) — Apple
- ProposedViewSize — Apple
- setContentCompressionResistancePriority(_:for:) — Apple
- TextKit — Apple
- Yang baru di TextKit dan tampilan teks — Apple