Untuk memadankan SwiftUI scroll view dengan kandungan pendek, ukur kandungan dalamannya dan tetapkan ketinggian viewport kepada nilai yang lebih kecil antara ukuran itu dengan had yang dipilih. Kandungan yang lebih panjang kemudian boleh ditatal dalam had tersebut. Panduan ini menunjukkan cara membina tingkah laku itu untuk panel label kecil dalam app Mac anda sendiri.
Mengapa scroll view yang pendek meninggalkan ruang kosong?
Mulakan dengan membezakan dua dimensi: ketinggian kandungan dan ketinggian viewport yang memaparkannya. Apple menerangkan ScrollView sebagai paparan yang menunjukkan sebahagian daripada kandungan asasnya dan melaraskan bahagian yang kelihatan semasa penatalan. Sekumpulan label yang pendek tidak dengan sendirinya menetapkan ketinggian viewport yang anda inginkan. Dokumentasi Apple tentang ScrollView menerangkan perbezaan ini.
Untuk panel desktop yang padat, tetapkan peraturan saiz yang jelas: viewportHeight = min(contentHeight, maximumHeight). Ukur kandungan pada lebar sebenar yang diterimanya, termasuk sebarang padding yang patut ditatal bersamanya. Gunakan hasilnya pada scroll view luar. Swift min(_:_:) memulangkan nilai yang lebih kecil, manakala SwiftUI frame(width:height:alignment:) menetapkan ketinggian tetap apabila argumen ketinggiannya diberikan. Swift min(_:_:), bingkai tetap SwiftUI.
Sebagai ilustrasi, andaikan had maksimum ialah 240 points. Kandungan berukuran 86 points menghasilkan viewport setinggi 86 points; kandungan berukuran 410 points menghasilkan viewport setinggi 240 points, dengan baki kandungan boleh dicapai melalui penatalan. Ini ialah input ilustrasi, bukannya ukuran daripada contoh di bawah.
| Perubahan | Perkara yang dikawal | Adakah ia menetapkan ketinggian padat yang dikehendaki? |
|---|---|---|
| Ketinggian kandungan yang diukur, dihadkan dan digunakan pada scroll view | Ketinggian viewport | Ya, dalam susun atur terkawal yang ditunjukkan di bawah |
.scrollBounceBehavior(.basedOnSize) |
Tingkah laku lantunan berbanding saiz kandungan | Tidak |
.fixedSize(horizontal: false, vertical: true) pada label |
Saiz menegak ideal kandungan | Ia membantu pengukuran; viewport luar masih memerlukan peraturan ketinggiannya |
Apple mendokumenkan .basedOnSize sebagai konfigurasi lantunan, dan fixedSize(horizontal:vertical:) sebagai cara menetapkan paparan pada saiz idealnya mengikut dimensi terpilih. Tiada satu pun huraian itu menetapkan ketinggian viewport berdasarkan kandungan. Tingkah laku lantunan scroll, Saiz tetap mengikut paksi.
Bina panel padat dengan ketinggian scroll maksimum
Gunakan pemasangan Xcode semasa yang macOS SDK-nya menyediakan overload onGeometryChange(for:of:action:) dengan satu nilai. Apple pada masa ini mendokumenkan bahawa overload itu tersedia mulai macOS 13.0. Contoh lengkap di bawah menyasarkan macOS 13.3 atau lebih baharu kerana ia turut menggunakan scrollBounceBehavior(_:axes:), yang tersedia mulai macOS 13.3. Panggilan balik perubahan geometri, Ketersediaan pengubah lantunan.
Cipta projek app macOS menggunakan SwiftUI. Gantikan pengisytiharan app dan paparan kandungan yang dijana dengan program satu fail ini, dan pastikan hanya satu pengisytiharan @main dalam target. Semua label ialah rentetan setempat sintetik. Togol Show more labels bertukar antara kandungan pendek dengan kandungan panjang.
import SwiftUI
@main
struct CompactLabelsApp: App {
var body: some Scene {
WindowGroup {
CompactLabelsView()
}
}
}
struct CompactLabelsView: View {
@State private var showMore = false
@State private var viewportHeight: CGFloat = 240
private var labels: [String] {
if showMore {
return (1...18).map { number in
"Sample label \(number): a longer description that can wrap when the panel becomes narrower."
}
}
return ["Sample label A", "Sample label B"]
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Toggle("Show more labels", isOn: $showMore)
ScrollView(.vertical) {
VStack(alignment: .leading, spacing: 8) {
if labels.isEmpty {
Text("No labels to display")
} else {
ForEach(labels.indices, id: \.self) { index in
Text(labels[index])
}
}
}
.font(.body)
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(12)
.onGeometryChange(for: CGFloat.self) { proxy in
min(proxy.size.height.rounded(.up), 240)
} action: { newHeight in
viewportHeight = newHeight
}
}
.frame(height: viewportHeight)
.scrollBounceBehavior(.basedOnSize, axes: .vertical)
.border(Color.secondary.opacity(0.4))
Text("The label panel grows up to 240 points.")
.font(.caption)
}
.padding(20)
.frame(
maxWidth: .infinity,
maxHeight: .infinity,
alignment: .topLeading
)
.frame(minWidth: 300, minHeight: 360)
}
}
Program ini telah diperiksa, tetapi belum dikompil atau dijalankan di sini kerana persekitaran ini tidak mempunyai pengkompil Swift atau runtime macOS. Bina program ini dalam Xcode dan lakukan pemeriksaan di bawah sebelum memasukkannya ke dalam antara muka sedia ada.
Mengapa pengukuran perlu dibuat di dalam scroll view
Panggilan balik geometri memerhatikan tindanan dalaman yang mempunyai padding. Mengukur scroll view luar akan melaporkan viewport yang telah anda tetapkan, lalu menghasilkan peraturan saiz yang berpusing dan bukannya menemui ketinggian yang diperlukan oleh kandungan.
Transformasi itu hanya memulangkan ketinggian yang dibundarkan dan dihadkan. Apple mengesyorkan agar geometri ditransformasikan kepada nilai yang diperlukan dan kemas kini yang luas dielakkan bagi perubahan geometri yang kerap. Di sini, perubahan pada kedudukan penatalan tidak mempengaruhi pengiraan, dan kandungan yang lebih tinggi daripada had tetap menghasilkan nilai 240-point yang sama. Panduan Apple tentang perubahan geometri.
Pembundaran ke atas mengelakkan penetapan viewport yang sengaja lebih pendek sedikit daripada kandungan yang diukur. Keadaan awal 240-point menyediakan viewport yang boleh digunakan sebelum pengukuran diterima; ini ialah nilai permulaan, bukannya ketinggian kandungan yang diukur. Panggilan balik kemudian menggantikannya.
Mengapa lebar dan susunan pengubah penting
Pastikan saiz mendatar fleksibel. Contoh ini menggunakan .fixedSize(horizontal: false, vertical: true) supaya label boleh menggunakan lebar yang tersedia sambil mengekalkan saiz menegak idealnya. Apple memberi amaran bahawa menetapkan dimensi ideal boleh menyebabkan paparan melebihi sempadan induknya; oleh itu, menetapkan kedua-dua dimensi bukan pilihan lalai yang sesuai untuk label yang membalut dalam panel sempit. Dokumentasi Apple tentang fixedSize(horizontal:vertical:).
Padding 12-point diletakkan sebelum pengubah pengukuran supaya ia termasuk dalam ketinggian kandungan yang diukur. Sempadan diletakkan selepas penetapan ketinggian luar untuk menandakan sempadan viewport. Susunan ini memudahkan anda melihat sama ada ruang yang tidak digunakan berada dalam panel boleh tatal atau di sekeliling tetingkapnya.
Sahkan kandungan pendek, pembalutan teks dan pengecilan
Gunakan langkah ini untuk menyemak tingkah laku dalam contoh dan sekali lagi selepas meletakkan panel dalam bekas sebenar app anda:
- Lancarkan app dengan
Show more labelsdimatikan. Sempadan sepatutnya mengelilingi dua label dan paddingnya, dengan kapsyen tepat di bawahnya. - Hidupkan
Show more labels. Panel sepatutnya membesar hingga had 240-point. Tatal untuk mengesahkan bahawa label terakhir masih boleh dicapai. - Sempitkan dan lebarkan tetingkap. Ketinggian label yang membalut sepatutnya berubah tanpa kehilangan baris terakhirnya.
- Matikan togol sekali lagi. Panel sepatutnya mengecil kembali kepada ketinggian kandungan pendek.
- Tukar tatasusunan pendek buat sementara waktu kepada
[]. Mesej jelasNo labels to displaysepatutnya menentukan ketinggian kandungan. - Ulangi langkah ini dengan gaya teks dan kawalan sekeliling yang digunakan oleh panel sebenar anda.
Keadaan kosong ialah pilihan antara muka yang disengajakan: contoh ini mengekalkan mesej dan padding apabila tatasusunan kosong. Jika tingkah laku yang dikehendaki ialah membuang seluruh panel, gugurkan seluruh scroll view secara bersyarat apabila tiada label. Itu keputusan produk yang berasingan daripada mengukur panel yang tidak kosong.
Tetingkap contoh di sekeliling panel mengekalkan ketinggian minimum. Kod ini menetapkan saiz viewport label; ia tidak meminta supaya tetingkap Mac mengecil setiap kali label dibuang. Sebarang ruang di bawah kapsyen ialah sebahagian daripada susun atur luar, di luar scroll view bersempadan.
Kenal pasti punca ruang kosong yang tertinggal atau label terpotong
Jika panel kekal tinggi, mula-mula periksa lokasi pengubah pengukuran dipasang. Pengubah itu sepatutnya memerhatikan tindanan kandungan selepas padding dalamannya. Kemudian periksa sama ada paparan induk menetapkan ketinggian atau ketinggian minimum yang memperuntukkan ruang di sekeliling panel. Apple mendokumenkan bahawa ketinggian bingkai yang diberikan ialah dimensi tetap, jadi bingkai luar yang berasingan boleh mengekalkan ruang walaupun viewport dalaman mengecil. Saiz bingkai SwiftUI.
Jika kandungan itu sendiri masih terlalu tinggi, buang pengembangan menegak daripada subpokok yang diukur. Untuk corak label kecil ini, elakkan spacer menegak atau bingkai kandungan yang ketinggian minimumnya ditentukan oleh viewport. Pilihan itu menjadikan ketinggian kandungan bergantung pada viewport, lalu menjejaskan pengukuran bebas yang diperlukan oleh peraturan saiz.
Jika padding bawah terpotong, pastikan padding itu disertakan sebelum pengukuran. Jika teks membalut secara berbeza selepas penyepaduan, periksa lebar sebenar yang diberikan oleh induk baharu. Ketinggian yang diukur pada satu lebar tidak patut dianggap sebagai ketinggian tetap pada lebar yang lain.
Pastikan had 240-point sesuai dengan antara muka yang menempatkannya. Contoh ini menetapkan saiz minimum tetingkap yang cukup besar untuk kawalan, padding, kapsyen dan panel yang dihadkan. Bekas yang lebih pendek memerlukan peruntukan ruang yang lebih kecil atau susun atur sekeliling yang berbeza; had tetap ini tidak mengira ruang yang tinggal di bawah kawalan lain secara automatik.
Gunakan pendekatan ini untuk sebilangan kecil label yang disusun serta-merta. Apple menerangkan LazyVStack sebagai paparan yang mencipta item hanya apabila diperlukan untuk pemaparan pada skrin. Oleh itu, jangan anggap bahawa menggantikan tindanan dalaman dengan tindanan malas mengekalkan strategi pengukuran kandungan lengkap yang sama. Untuk koleksi besar, viewport stabil dengan kandungan malas biasanya reka bentuk yang lebih sesuai. Dokumentasi Apple tentang LazyVStack.
Bilakah pengukuran AppKit dan TextKit sesuai digunakan?
Untuk panel label SwiftUI ini, ukur subpokok SwiftUI yang dipaparkan. Pengukuran teks AppKit akan memperkenalkan satu lagi konfigurasi susun atur yang mesti sepadan dengan label yang dipaparkan.
Jika app anda sudah memiliki NSScrollView, kekalkan perbezaan yang sama antara dokumen dengan viewport. Apple mentakrifkan documentView sebagai paparan yang ditatal, manakala contentSize ialah saiz paparan kandungan scroll view. Walaupun namanya begitu, contentSize bukan ukuran keseluruhan dokumen. AppKit documentView, AppKit contentSize.
Untuk susun atur TextKit sedia ada yang dibina menggunakan NSLayoutManager, usedRect(for:) melaporkan segi empat sempadan glyph yang sudah disusun dalam bekas teks; Apple menyatakan dengan jelas bahawa pertanyaan terhadapnya tidak menjana glyph atau melaksanakan susun atur. Apabila susun atur bekas yang lengkap diperlukan, ensureLayout(for:) ialah kaedah yang didokumenkan untuk memaksa susun atur itu dilakukan terlebih dahulu. TextKit usedRect(for:), TextKit ensureLayout(for:).
Walaupun begitu, segi empat glyph bukan bingkai lengkap paparan teks. Apple mendokumenkan ruang tambahan di sekeliling bekas teks melalui textContainerInset, serta padding serpihan baris di dalamnya. Ambil kira konfigurasi sebenar paparan teks dan jangan salin ketinggian glyph terus ke dalam viewport boleh tatal. Kaedah ini menerangkan laluan NSLayoutManager; ia bukan resipi saiz sejagat untuk setiap komponen teks. AppKit textContainerInset.
Untuk panel label SwiftUI yang kecil, penyelesaian praktikalnya ialah mengekalkan lebar yang fleksibel, mengukur kandungan dalaman berpadding dan mengehadkan ketinggian viewport luar. Sahkan hasilnya untuk kandungan pendek dan panjang, kemudian uji di dalam susun atur induk sebenar supaya kekangan sekeliling menyokong tingkah laku padat yang sama.
Sumber
- ScrollView — Apple
- min(::) — Apple
- frame(width:height:alignment:) — Apple
- scrollBounceBehavior(_:axes:) — Apple
- fixedSize(horizontal:vertical:) — Apple
- onGeometryChange(for:of:action:) — Apple
- LazyVStack — Apple
- documentView — Apple
- contentSize — Apple
- usedRect(for:) — Apple
- ensureLayout(for:) — Apple
- textContainerInset — Apple