Untuk memperbaiki konten SwiftUI yang bergeser ke luar layar saat ukuran jendela Mac diubah, pertama-tama cari tahu apakah gambar bergerak melewati batas tata letaknya, konten menolak untuk menyusut, atau tampilan AppKit yang disematkan berhenti mengikuti kontainernya. Kemudian perbaiki batas yang bermasalah itu.
Panduan ini ditujukan bagi pengembang yang memperbaiki antarmuka aplikasi Mac mereka sendiri yang ukurannya dapat diubah: menjaga label tetap terbaca, kontrol tetap dapat dijangkau, dan konten yang lebih panjang tetap dapat digulir. Urutan diagnosis di bawah menggunakan label sintetis dan membedakan tata letak SwiftUI, ukuran jendela, serta pembungkusan teks AppKit.
Cari tahu batas mana yang gagal saat ukuran diubah
Tata letak SwiftUI dimulai dengan proposal ukuran dari induknya. Tampilan anak memilih ukurannya sebagai tanggapan; proposal bukan instruksi untuk memotong semuanya ke persegi panjang tersebut. Apple mendokumentasikan proposal ukuran minimum, maksimum, dan ideal dalam ProposedViewSize. Hal ini menjelaskan mengapa mempersempit jendela belum tentu membuat setiap turunannya lebih sempit.
Mulailah dengan pengujian yang dapat diulang: buka jendela aplikasi, persempit sampai kegagalan pertama muncul, lalu lebarkan kembali. Amati elemen pertama yang terdampak, bukan susunan akhirnya. Apakah label melampaui kontainernya? Apakah seluruh panel bergeser? Apakah teks tetap menggunakan lebar pembungkusan sebelumnya?
Untuk sementara, tambahkan .border(Color.orange) pada tampilan anak yang dicurigai dan .border(Color.blue) pada bingkai atau kontainer pembungkusnya. Letakkan modifier di tingkat yang berbeda agar Anda dapat membandingkan persegi panjangnya. Dokumentasi offset Apple menunjukkan teknik garis tepi ini untuk membedakan posisi tata letak asli dari gambar yang bergeser.
| Yang Anda amati | Periksa terlebih dahulu | Tindakan berikutnya yang berguna |
|---|---|---|
| Gambar bergerak keluar dari persegi panjang yang tetap stabil | offset, position, perhitungan koordinat |
Hapus modifier penempatan satu per satu |
| Label tetap lebar saat kontainernya menyempit | fixedSize horizontal, lebar eksplisit |
Biarkan label menerima lebar yang dibatasi |
| Kontrol tidak muat dalam baris horizontal | Gabungan lebar kontrol dan jarak | Sediakan susunan vertikal sebagai alternatif |
| Konten bagian bawah menghilang saat tinggi berkurang | Perilaku luapan yang tidak tersedia | Jadikan wilayah konten dapat digulir |
| Panel SwiftUI yang di-host bergeser di dalam AppKit | Batas tampilan host dan batasan ukuran | Bandingkan ukuran host dengan kebutuhan konten |
| Tampilan teks AppKit menyempit, tetapi barisnya tetap lebar | Pelacakan lebar kontainer teks | Periksa konfigurasi tampilan teks dan kontainer teks |
Ini adalah hipotesis diagnosis yang disimpulkan dari perilaku yang didokumentasikan, bukan jaminan bahwa suatu gejala tertentu hanya memiliki satu penyebab. Ubah satu hal yang dicurigai pada satu waktu dan ulangi gerakan pengubahan ukuran yang sama. Jika mengganti tampilan anak yang kompleks dengan Text("Sample panel") menghilangkan masalahnya, tambahkan kembali lapisan tampilan anak itu secara bertahap.
Hapus aturan penempatan dan ukuran yang tidak mengikuti ukuran jendela
Periksa offset dan penempatan berbasis titik tengah
offset menggeser konten yang ditampilkan tanpa mengubah dimensi asli tampilan. Karena itu, offset horizontal positif dapat mendorong gambar melewati tepi kanan, meskipun induknya masih menyediakan ruang untuk posisi semula. Hapus offset sementara; jika gambar kembali ke dalam kontainer, ganti penyesuaian itu dengan susunan yang dinyatakan melalui stack, padding, atau perataan bingkai. Lihat perilaku offset dari Apple.
position(x:y:) menempatkan titik tengah tampilan pada koordinat dalam ruang koordinat induknya. Menganggap koordinat tersebut sebagai sudut kiri atas menimbulkan kesalahan yang berkaitan dengan ukuran tampilan anak. Posisi yang dihitung satu kali juga bisa menjadi tidak sesuai saat ukuran induknya berubah. Untuk panel label dan kontrol biasa, gunakan perataan yang menyatakan hubungan yang diinginkan dengan kontainer. Apple menjelaskan perilaku berbasis titik tengah ini dalam position(x:y:).
Sebagai contoh, .frame(maxWidth: .infinity, alignment: .leading) menyatakan perataan leading di dalam bingkai fleksibel. Ini tidak menghapus persyaratan ukuran milik tampilan anak yang terlalu besar. Dokumentasi bingkai fleksibel Apple menjelaskan cara bingkai membatasi proposal dan menempatkan tampilan anaknya.
Periksa lebar tetap dan ukuran ideal teks
Periksa pengaturan lebar eksplisit dan penggunaan horizontal fixedSize pada cabang yang bermasalah. Apple secara khusus mendokumentasikan bahwa .fixedSize(horizontal: true, vertical: false) membuat teks mempertahankan lebar ideal yang dibutuhkan seluruh string, sehingga berpotensi melampaui batas induknya. Untuk teks isi yang perlu dibungkus, hapus persyaratan horizontal tersebut. Jika tinggi penuh teks yang telah dibungkus perlu dipertahankan, pertimbangkan .fixedSize(horizontal: false, vertical: true). Lihat fixedSize(horizontal:vertical:).
Mempertahankan ukuran vertikal berarti harus ada tempat untuk tinggi itu. Paragraf yang bertambah tinggi di dalam kontainer pendek yang tidak dapat digulir tetap bisa sulit dijangkau. Gunakan wilayah gulir vertikal jika tugas mengharuskan semua teks dapat diakses; Apple mendokumentasikan sumbu gulir yang didukung dalam ScrollView.
Periksa kembali perhitungan berbasis geometri
GeometryReader mendefinisikan konten menggunakan ukuran dan ruang koordinatnya sendiri, serta mengembalikan ukuran pilihan yang fleksibel kepada induknya. Menambahkannya di sekeliling tampilan mengubah struktur tata letak, jadi ini bukan pembungkus pengukuran yang netral. Jika pergeseran mulai terjadi setelah perhitungan geometri ditambahkan, untuk sementara ganti cabang tersebut dengan stack dan bingkai biasa. Apple menjelaskan sifat-sifat ini dalam GeometryReader.
Untuk tata letak proporsional yang memang membutuhkan geometri, turunkan dimensi dari kontainer relevan yang ukurannya berlaku saat ini. Periksa state yang menyimpan lebar yang diambil saat tampilan pertama kali muncul. Periksa juga apakah dimensi yang diukur digunakan untuk mengubah ukuran wilayah yang sama dengan yang sedang diukur; menghapus ketergantungan tersebut adalah pengujian isolasi yang berguna.
Buat tata letak ringkas dengan perilaku luapan yang jelas
Contoh lengkap berikut menunjukkan tiga pilihan terpisah: teks dibungkus, susunan kontrol berubah saat diperlukan, dan luapan vertikal tetap dapat diakses. Dimensi yang digunakan hanyalah contoh masukan desain, bukan nilai yang ditetapkan Apple.
Buat aplikasi SwiftUI macOS di Xcode, atur target deployment ke macOS 13 atau lebih baru, lalu ganti deklarasi aplikasi dan tampilan konten yang dibuat otomatis dengan satu berkas ini. Pertahankan hanya satu deklarasi @main. Contoh ini menggunakan ViewThatFits dan API kemampuan mengubah ukuran jendela yang tersedia di macOS 13. Apple mendokumentasikan perilakunya dalam ViewThatFits dan windowResizability(_:).
import SwiftUI
@main
struct ResizeExampleApp: App {
var body: some Scene {
WindowGroup {
ResizeExampleView()
.frame(minWidth: 280, minHeight: 220)
}
.windowResizability(.contentMinSize)
}
}
struct ResizeExampleView: View {
@State private var selection = "Sample A"
private let details = Array(
repeating: "Sample notes wrap as the panel becomes narrower.",
count: 12
).joined(separator: " ")
var body: some View {
ScrollView(.vertical) {
VStack(alignment: .leading, spacing: 16) {
Text("Sample panel")
.font(.title2)
Text(details)
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading)
ViewThatFits(in: .horizontal) {
HStack(spacing: 12) {
Button("Choose Sample A") {
selection = "Sample A"
}
Button("Choose Sample B") {
selection = "Sample B"
}
}
.fixedSize(horizontal: true, vertical: false)
VStack(alignment: .leading, spacing: 12) {
Button("Choose Sample A") {
selection = "Sample A"
}
Button("Choose Sample B") {
selection = "Sample B"
}
}
}
Text("Selected: \(selection)")
}
.padding(20)
.frame(
minWidth: 0,
maxWidth: .infinity,
alignment: .leading
)
}
}
}
ViewThatFits mengevaluasi alternatif sesuai urutan deklarasinya dan memilih alternatif pertama yang ukuran idealnya muat pada sumbu yang dibatasi. Di sini, pencocokan horizontal memilih antara baris tombol dan stack vertikal. Baris tersebut sengaja mempertahankan ukuran ideal horizontalnya agar baris yang terkompresi tidak menjadi pilihan yang diutamakan. Pastikan kedua alternatif menyediakan tindakan yang setara. Lihat aturan pemilihan Apple.
Bingkai luar memungkinkan kolom konten mengikuti lebar yang tersedia, sedangkan tampilan gulir vertikal menyediakan akses saat pembungkusan menambah tinggi. Susunan ini mengasumsikan kontrol individual muat pada lebar minimum yang dipilih. Label yang lebih panjang atau font yang lebih besar mungkin memerlukan lebar minimum berbeda atau susunan ringkas yang berbeda.
Contoh ini telah diperiksa untuk memastikan deklarasi konsisten dan API tersedia; contoh ini tidak dikompilasi di sini karena kompiler Swift tidak tersedia. Jalankan di macOS untuk memverifikasi perilaku jendela dan teks yang sebenarnya.
Atur batas jendela setelah memperbaiki tata letak konten
Ukuran minimum jendela yang sesuai mencegah antarmuka menjadi lebih kecil daripada susunan yang didukungnya. Dengan .contentMinSize, SwiftUI menurunkan ukuran minimum jendela dari kontennya dan tidak menetapkan ukuran maksimum. Dalam contoh ini, bingkai akar menyediakan masukan lebar dan tinggi minimum. Lihat contentMinSize.
Tentukan ukuran minimum tersebut dengan menguji susunan ringkas menggunakan label terpanjang yang didukung. Meningkatkan ukuran minimum sampai tampilan yang salah tempat terlihat dapat menyamarkan kesalahan tata letak yang sebenarnya. Hapus dahulu pergeseran yang tidak disengaja dan tampilan anak yang terlalu besar; lalu tetapkan dimensi terkecil yang masih memungkinkan tugas yang dimaksud digunakan dengan baik.
Jika AppKit mengelola jendela dan menjadi host SwiftUI, periksa batas hosting secara terpisah. Pada macOS 13 dan versi lebih baru, NSHostingView.sizingOptions mengontrol batasan yang diturunkan dari konten SwiftUI. Ketika host menjadi contentView jendela, host juga memperbarui ukuran minimum dan maksimum konten jendela. Apple juga mendokumentasikan bahwa konten akan dipusatkan ketika bingkai host berbeda dari ukuran yang dibutuhkan konten SwiftUI-nya. Periksa hubungan ini sebelum menambahkan offset sebagai kompensasi. Lihat NSHostingView sizingOptions.
Periksa AppKit dan TextKit hanya jika tampilan yang bermasalah menggunakannya
Biarkan SwiftUI mengatur ukuran tampilan akar yang direpresentasikan
Untuk NSViewRepresentable, SwiftUI mengontrol frame dan bounds pada tampilan AppKit yang direpresentasikan. Apple secara eksplisit memperingatkan bahwa mengatur properti tersebut sendiri bertentangan dengan SwiftUI dan menghasilkan perilaku yang tidak terdefinisi. Jika pengendali perubahan ukuran Anda berulang kali menetapkan bingkai akar yang direpresentasikan, hapus pengelolaan yang saling bersaing itu dan nyatakan ukuran melalui batas SwiftUI. Lihat NSViewRepresentable.
Jika pengukuran khusus diperlukan, macOS 13 dan versi lebih baru menyediakan sizeThatFits(_:nsView:context:). API ini dapat menerima beberapa proposal selama satu proses tata letak; mengembalikan nil meminta pengukuran ukuran bawaan. Buat hasilnya bergantung pada proposal dan konten saat ini, bukan lebar jendela sebelumnya. Dimensi proposal yang tidak ditentukan memerlukan kebijakan pengukuran yang disengaja, bukan otomatis dianggap nol. Lihat ukuran representable.
Pengikatan opsional di Swift menyediakan cara untuk memeriksa dan mengambil dimensi yang diusulkan sebelum menggunakannya. Ini berguna saat menerapkan kebijakan ukuran tanpa memaksa membuka lebar atau tinggi opsional. Perilaku bahasa ini didokumentasikan dalam Dasar-Dasar: Pengikatan Opsional.
Pisahkan lebar pembungkusan teks dari lebar tampilan teks
Jika tampilan anak yang bermasalah adalah NSTextView, bandingkan lebarnya saat ini dengan lebar kontainer teksnya. Apple mendokumentasikan bahwa NSTextContainer.widthTracksTextView secara default bernilai false; mengaturnya ke true membuat kontainer menyesuaikan lebarnya saat lebar tampilan teks berubah. Inilah pengaturan yang relevan ketika baris harus menyesuaikan ulang dengan lebar tampilan teks. Lihat widthTracksTextView.
Periksa juga isHorizontallyResizable. Properti ini mengontrol apakah lebar tampilan teks berubah agar sesuai dengan teksnya, yang berbeda dari persyaratan untuk membungkus teks di dalam lebar yang telah ditetapkan. Untuk wilayah teks yang membungkus dan menggulir secara vertikal, atur kedua kebijakan tersebut secara konsisten dan pastikan tata letak yang menampungnya benar-benar mengubah ukuran tampilan teks. Lihat isHorizontallyResizable.
Pertahankan mesin teks yang digunakan implementasi Anda. Apple mendokumentasikan bahwa mengakses layoutManager pada NSTextView atau kontainer teksnya dapat mengalihkannya ke mode kompatibilitas yang menggunakan NSLayoutManager. Jangan menambahkan akses itu begitu saja saat mendiagnosis tampilan TextKit 2. Gunakan API yang sesuai dengan mesin yang ada dan target deployment. Lihat NSTextView.
Verifikasi perbaikan dengan mengubah lebar dan tinggi
Ikuti urutan terakhir ini di aplikasi Anda:
- Perlahan persempit jendela melewati titik yang sebelumnya bermasalah, lalu lebarkan kembali.
- Kurangi tinggi secara terpisah dan pastikan semua konten yang diperlukan tetap dapat diakses.
- Ulangi dengan label sintetis yang panjang dan teks multibaris.
- Aktifkan setiap kontrol dalam susunan lebar maupun ringkas.
- Untuk tampilan teks AppKit, pastikan pembungkusan mengikuti lebar saat ini pada kedua arah pengubahan ukuran.
Perbaikan yang berhasil menjaga penempatan tetap mengikuti kontainer saat ini, memberi konten susunan ringkas yang dapat digunakan, dan menyediakan akses ke konten yang meluap. Setelah perilaku tersebut tetap benar dalam pengubahan ukuran berulang, hapus garis tepi sementara dan pertahankan ukuran minimum jendela yang didukung oleh kebutuhan antarmuka.
Sumber
- ProposedViewSize — Apple
- offset(x:y:) — Apple
- position(x:y:) — Apple
- frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) — Apple
- fixedSize(horizontal:vertical:) — Apple
- ScrollView — Apple
- GeometryReader — Apple
- ViewThatFits — Apple
- windowResizability(_:) — Apple
- contentMinSize — Apple
- sizingOptions — Apple
- NSViewRepresentable — Apple
- sizeThatFits(_:nsView:context:) — Apple
- Dasar-Dasar — Swift.org
- widthTracksTextView — Apple
- isHorizontallyResizable — Apple
- NSTextView — Apple