Untuk baris label kecil dalam aplikasi Mac, gunakan ViewThatFits untuk memilih prefiks utuh terpanjang yang muat, dengan hitungan tersembunyikannya disertakan. Tutorial ini membangun baris tersebut tanpa memotong label atau memperbarui state selama tata letak.
Tugas konkretnya adalah jendela macOS yang bisa diubah ukurannya berisi enam label sintetis. Pertahankan urutannya, tampilkan hanya label utuh, dan tampilkan badge seperti 3 more untuk suffix yang dihilangkan. Solusi ini mengasumsikan cukup ruang horizontal untuk badge hanya-hitung dan cukup ruang vertikal untuk satu baris.
Pilih alternatif baris utuh
Apple mendokumentasikan ViewThatFits sebagai memilih anak pertama yang ukuran idealnya muat dengan ukuran yang diusulkan pada sumbu yang dibatasi. Tersedia pada macOS 13 dan yang lebih baru. Gunakan .horizontal di sini karena tugas ini berkaitan dengan lebar baris; antarmuka sekitar harus menyediakan tinggi baris. ViewThatFits
Untuk enam label, sediakan tujuh alternatif dalam urutan ini: semua enam label tanpa badge, lima label ditambah 1 more, empat ditambah 2 more, dan seterusnya, berakhir dengan hanya 6 more. Setiap alternatif adalah baris utuh. Badge-nya berpartisipasi dalam keputusan kecocokan, sehingga menambahkan hitungan tidak akan secara tak terduga menggeser label terlihat terakhir.
Ini menerapkan kebijakan prefiks: label pertama yang panjang dapat menyebabkan setiap label dihilangkan, bahkan jika label pendek yang lebih akhir akan muat. Itu sengaja. Jika tugas Anda malah mengizinkan melewati label individual, definisikan kebijakan pemilihan yang berbeda itu sebelum mengimplementasikannya.
Terapkan fixedSize() ke setiap baris kandidat sehingga label dan badge-nya mempertahankan dimensi idealnya. Apple menjelaskan bahwa modifier ini dapat membuat view melebihi batas parent-nya; ia tidak secara independen menjamin kecocokan. Di sini, container pemilihan yang mengelilingi melakukan keputusan kecocokan. fixedSize()
Bangun contoh macOS lengkap
Buat aplikasi SwiftUI macOS di Xcode dan tetapkan target deployment-nya ke macOS 13 atau yang lebih baru. Ganti deklarasi app dan content view yang dihasilkan dengan program single-file berikut. Pertahankan tepat satu deklarasi app yang ditandai @main di target.
Contoh ini menggunakan identifier eksplisit bukan teks label sebagai identitas. Dokumentasi ForEach Apple memerlukan elemen yang diidentifikasi atau key path identifier eksplisit; identifier yang berbeda juga memungkinkan dua item berbagi judul yang ditampilkan yang sama. ForEach
import SwiftUI
struct DemoItem: Identifiable {
let id: Int
let title: String
}
struct ItemChip: View {
let title: String
var body: some View {
Text(title)
.lineLimit(1)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color.blue.opacity(0.12))
)
}
}
struct PrefixCandidate: View {
let items: [DemoItem]
let visibleCount: Int
var body: some View {
HStack(spacing: 8) {
ForEach(items.prefix(visibleCount)) { item in
ItemChip(title: item.title)
}
if visibleCount < items.count {
Text("\(items.count - visibleCount) more")
.lineLimit(1)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color.gray.opacity(0.15))
)
}
}
.fixedSize()
}
}
struct WholeItemRow: View {
let items: [DemoItem]
var body: some View {
if items.isEmpty {
Text("No items")
.fixedSize()
} else {
ViewThatFits(in: .horizontal) {
ForEach(
Array((0...items.count).reversed()),
id: \.self
) { visibleCount in
PrefixCandidate(
items: items,
visibleCount: visibleCount
)
}
}
}
}
}
struct ContentView: View {
let items: [DemoItem] = [
DemoItem(id: 1, title: "Sketch"),
DemoItem(id: 2, title: "Color samples"),
DemoItem(id: 3, title: "Shapes"),
DemoItem(id: 4, title: "Paper textures"),
DemoItem(id: 5, title: "Layout notes"),
DemoItem(id: 6, title: "Preview")
]
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Sample labels")
.font(.headline)
WholeItemRow(items: items)
.frame(maxWidth: .infinity, alignment: .leading)
Text("Resize the window to change how many labels fit.")
.foregroundStyle(.secondary)
}
.font(.system(size: 14))
.padding(24)
.frame(minWidth: 320, idealWidth: 680, minHeight: 160)
}
}
@main
struct WholeItemsDemoApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Identifier kandidat adalah hitung terlihat yang mungkin, menurun dari hitung item ke nol. Di dalam setiap kandidat, identifier item mengidentifikasi label. Setiap iterasi menciptakan satu PrefixCandidate, yang berisi satu baris horizontal.
Frame fleksibel luar memungkinkan baris menerima ruang horizontal yang tersedia sambil menjaga konten terpilih tetap rata ke tepi leading. Pertahankan fixedSize() pada kandidat di dalam ViewThatFits. Menerapkannya ke seluruh container pemilihan akan mengubah bagaimana container itu merespons proposal lebar parent-nya.
Ini adalah contoh sumber lengkap, tetapi tidak dikompilasi atau dijalankan di sini karena toolchain Swift tidak tersedia. Aritmatika ilustratif di bagian selanjutnya diperiksa secara terpisah; lebar teks aktual harus diverifikasi di aplikasi Mac.
Sertakan badge saat menghitung apa yang muat
Aturan keputusan yang berguna adalah: pilih hitung terlihat terbesar yang seluruh kandidatnya muat. Hitung tersembunyi selalu total hitung item dikurangi hitung terlihat kandidat itu. Tidak ada estimasi terpisah untuk disinkronkan.
Pertimbangkan baris empat-item ilustratif dengan lebar 72, 96, 64, dan 88 poin, diukur termasuk padding item. Asumsikan spasi 8-poin dan badge 60-poin untuk setiap hitung tersembunyi non-nol. Ini adalah input perhitungan yang dibuat-buat, bukan pengukuran aplikasi contoh.
Saat keempat item terlihat, lebar yang dibutuhkan adalah 72 + 96 + 64 + 88 + 3 × 8 = 344 poin. Dengan tiga item terlihat, badge menggantikan suffix yang dihilangkan, sehingga lebarnya menjadi 72 + 96 + 64 + 3 × 8 + 60 = 316 poin. Tiga gap diperlukan karena kandidat berisi tiga item dan satu badge.
| Lebar tersedia | Item terlihat | Item tersembunyi | Lebar kandidat terpilih |
|---|---|---|---|
| 344 points | 4 | 0 | 344 points |
| 343 points | 3 | 1 | 316 points |
| 300 points | 2 | 2 | 244 points |
| 220 points | 1 | 3 | 140 points |
| 60 points | 0 | 4 | 60 points |
Dalam program, setiap kandidat berisi teks badge aktualnya. Oleh karena itu, ia mengakomodasi perbedaan antara string badge tanpa bergantung pada asumsi lebar konstan ilustrasi. Lebar kandidat tidak perlu berubah dengan jumlah seragam; hitung terlihat yang menurun mengekspresikan preferensi pemilihan.
Kandidat hanya-hitung menetapkan lebar minimum yang dapat digunakan. Jika bahkan badge itu tidak muat, menampilkan hitung penuh dalam lebar yang tersedia adalah mustahil di bawah aturan ini. Sediakan lebar minimum yang cukup, izinkan susunan berbeda, atau pindahkan hitung ke tempat lain. Jangan memperlakukan clipping sebagai hasil whole-item yang sukses.
Contoh menyediakan lebar konten minimum dan font eksplisit yang sederhana untuk badge sintetis pendeknya. Jika Anda mengubah redaksi, font, padding, atau tata letak sekitar, verifikasi kasus hanya-hitung lagi. Lebar minimum yang dipilih untuk contoh ini bukan pengukuran badge universal.
Pisahkan pemilihan tata letak dari siklus hidup view
Hitung tersembunyi milik input immutable setiap kandidat. Program tidak menulis binding dari metode pengukuran, menjadwalkan pembaruan hitung asinkron, atau menyimpulkan visibilitas dari callback appearance. Memilih baris juga memilih badge yang cocoknya.
Jaga konten kandidat yang diulang tetap sederhana. Mengulang label di seluruh alternatif tidak membuat satu identitas bersama untuk label tersebut di semua alternatif. Apple menjelaskan bahwa penyimpanan state terkait dengan identitas view dan bahwa mengubah identitas dapat menggantikan state. Demystify SwiftUI
Jika Anda nanti mengganti label dengan kontrol yang dapat diedit, simpan nilai bersama mereka di ancestor atau model umum dan teruskan input atau binding yang sesuai ke setiap presentasi. Apple menyarankan menaruh state bersama di ancestor paling tidak umum dari view yang membutuhkannya. Jangan bergantung pada state lokal kandidat yang bertahan melalui perubahan presentasi. Managing user interface state
Solusi ini menampilkan hitung tersembunyi di dalam baris terpilih. Ia tidak mengekspos hitung terpilih ke view saudara atau model aplikasi. Inisialisasi ViewThatFits yang didokumentasikan Apple menerima sumbu yang dibatasi dan konten kandidat; ia tidak menyediakan binding hitung-terpilih. Perlakukan mengekspor nilai itu sebagai persyaratan desain tambahan. init(in:content:)
Ketahui kapan tata letak custom memerlukan desain berbeda
Custom Layout sesuai ketika Anda butuh geometri khusus. Apple mendefinisikan tanggung jawab intinya sebagai melaporkan ukuran komposit dan menugaskan posisi subview melalui layout proxy. Proxy mengekspos operasi pengukuran dan penempatan bukan akses langsung ke view yang mendasari. Layout
Jangan implementasikan penghilangan hanya dengan menghentikan penempatan setelah item terakhir yang muat. Dokumentasi LayoutSubview Apple menginstruksikan Anda untuk menempatkan setiap subview sekali. Melewati penempatan bukan mekanisme penyembunyian yang terdokumentasi, dan layout proxy tidak menyediakan operasi penulisan-ulang konten umum untuk mengubah subview lain menjadi label hitung baru. LayoutSubview
Jika Anda membangun custom layout, pertahankan kesepakatan antara pengukuran dan penempatan, termasuk spasi dan proposal ukuran yang digunakan. Apple secara eksplisit mewajibkan komputasi itu konsisten. Kontrak itu tetap berlaku ketika layout memeriksa beberapa susunan mungkin. placeSubviews(in:proposal:subviews:cache:)
Pendekatan kandidat sengaja dibatasi. Untuk enam item ia mendeskripsikan tujuh baris kandidat dan 21 penampilan item di seluruh baris tersebut. Secara umum, jumlah penampilan item tumbuh sebagai n × (n + 1) / 2. Ini adalah hitung struktural, bukan benchmark performa atau klaim bahwa setiap kandidat dirender pada setiap update. Gunakan untuk koleksi label kecil; profil sebelum memperluasnya ke koleksi besar atau konten mahal.
Verifikasi baris di batas-batasnya
Jalankan aplikasi dan ubah ukuran jendelanya perlahan. Periksa kondisi berikut sebelum menyesuaikan komponen:
- Pada lebar lebar, setiap label muncul dan tidak ada badge
0 more. - Saat jendela menyempit, label hilang dari ujung tanpa label parsial atau elipsis.
- Hitung item terlihat ditambah hitung tersembunyi badge selalu sama dengan enam.
- Dengan satu label pertama yang sangat panjang, kandidat hanya-hitung tetap tersedia.
- Dengan array kosong, baris menampilkan
No items. - Dengan judul duplikat dan identifier berbeda, kedua item tetap terepresentasikan dalam hitungan.
- Setelah mengubah font atau redaksi badge, kandidat hanya-hitung tetap muat di lebar terkecil yang didukung.
Untuk baris kecil yang terurut, alternatif utuh membuat aturan kecocokan mudah diperiksa: setiap kandidat menyertakan label, spasi, dan hitung tersembunyi yang tepatnya. Jaga kondisi lebar-minimum tetap eksplisit dan konten yang diulang tetap ringan, dan baris dapat beradaptasi dengan perubahan ukuran jendela tanpa item parsial atau siklus pembaruan-hitung terpisah.
Sumber
- ViewThatFits — Apple
- fixedSize() — Apple
- ForEach — Apple
- Demystify SwiftUI — Apple
- Managing user interface state — Apple
- init(in:content:) — Apple
- Layout — Apple
- LayoutSubview — Apple
- placeSubviews(in:proposal:subviews:cache:) — Apple