Berikan rupa lalai kepada komponen generik SwiftUI dengan menambah pemula yang dikekang kepada jenis kandungan konkrit. Kekalkan pemula pembina yang berasingan untuk penyesuaian. Panduan ini membina panel boleh guna semula untuk tetingkap macOS dan menerangkan bila inferens argumen lalai Swift 5.7 boleh memudahkan antara muka.
Tentukan dua panggilan yang perlu disokong oleh komponen anda
Tugas pembaca mudah: bina panel desktop yang kelihatan lengkap apabila dipanggil dengan tajuk sahaja, sambil membenarkan skrin lain membekalkan kandungannya sendiri. Tajuk, jarak dan sempadan di sekelilingnya perlu kekal konsisten dalam kedua-dua keadaan.
Antara muka yang dimaksudkan ialah DesktopPanel(title: "Workspace") untuk persembahan lalai dan DesktopPanel(title: "Workspace") { Text("Three sample items") } untuk kandungan tersuai. Panggilan ini menghasilkan pengkhususan berbeza bagi komponen generik yang sama.
Parameter generik Swift mewakili jenis konkrit. Kekangan seperti Content: View menghendaki pematuhan; syarat jenis sama seperti Content == DefaultPanelContent memilih satu jenis tertentu. Rujukan bahasa mendokumentasikan kedua-dua syarat tersebut dan resolusi lebihan beban pemula melalui kekangan. Generic Parameters and Arguments
Oleh itu, komponen ini mempunyai dua tanggungjawab yang jelas: body memiliki rupa bersama panel, manakala pemulanya menentukan kandungan yang diletakkan di dalamnya. Kandungan lalai mendapat jenis paparan bernama supaya pemula kemudahan boleh merujuk pengkhususan yang stabil dan mudah dibaca.
Bina contoh macOS yang lengkap
Gunakan sasaran apl SwiftUI macOS dengan sasaran penggunaan macOS 11 atau lebih baharu dan pengkompil Swift 5.7 atau lebih baharu untuk panduan ini. Versi minimum macOS ini menyokong struktur App dan WindowGroup yang digunakan di bawah; Apple mendokumentasikan WindowGroup sebagai bekas adegan untuk hierarki paparan tetingkap. WindowGroup
Gantikan titik masuk apl yang dijana dengan fail sumber lengkap ini, dan buang ContentView.swift yang dijana jika fail itu mengisytiharkan ContentView yang lain. Kekalkan tepat satu pengisytiharan apl @main dalam sasaran tersebut. Semua nilai yang dipaparkan di bawah ialah contoh tempatan sintetik.
import SwiftUI
struct DefaultPanelContent: View {
var body: some View {
VStack(alignment: .leading, spacing: 6) {
Text("Sample workspace")
.font(.headline)
Text("Choose a layout to begin.")
.foregroundColor(.secondary)
}
}
}
struct DesktopPanel<Content: View>: View {
private let title: String
private let content: Content
init(
title: String,
@ViewBuilder content: () -> Content
) {
self.title = title
self.content = content()
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text(title)
.font(.title2)
Divider()
content
}
.padding(16)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.gray.opacity(0.08))
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray, lineWidth: 1)
)
}
}
extension DesktopPanel where Content == DefaultPanelContent {
init(title: String) {
self.init(title: title) {
DefaultPanelContent()
}
}
}
struct ContentView: View {
var body: some View {
VStack(alignment: .leading, spacing: 20) {
DesktopPanel(title: "Default panel")
DesktopPanel(title: "Custom panel") {
HStack {
Text("Sample items")
Spacer()
Text("3")
.font(.headline)
}
Text("Layout: Compact")
.foregroundColor(.secondary)
}
}
.padding(24)
.frame(minWidth: 420, minHeight: 300)
}
}
@main
struct PanelExampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Bina dan jalankan sasaran macOS. Hasil yang dimaksudkan ialah dua panel dengan gaya luaran yang sama: panel pertama mengandungi teks ruang kerja lalai, manakala panel kedua mengandungi baris kiraan diikuti penerangan susun atur. Persekitaran ini tiada pengkompil Swift atau SDK Apple, jadi contoh ini telah diperiksa tetapi tidak dapat dikompil di sini.
Panduan Apple tentang paparan tersuai menyokong komposisi ini: isytiharkan pematuh View, laksanakan body-nya, dan susun paparan terbina dalam serta tersuai ke dalam hierarki. Panduan itu juga menerangkan bahawa pengubah mengembalikan paparan yang diubah, sebab itulah gaya bersama diletakkan di sekeliling kandungan panel. Declaring a custom view
Sebab pemula terkekang menentukan jenis generik
Pemula tersuai menerima penutupan yang mengembalikan Content. Memanggil content() membina nilai yang disimpan dalam panel. Penutupan itu digunakan semasa pemulaan, jadi pelaksanaan ini tidak menyimpannya atau memerlukan @escaping.
Untuk panggilan tanpa pembina, sambungan itu menyediakan init(title:) khusus apabila Content ialah DefaultPanelContent. Syarat itu memberikan maklumat jenis yang tiada. Pelaksanaannya kemudian menyerahkan tugas kepada pemula pembina, memastikan pemulaan sifat berlaku di satu tempat sahaja.
Nilai lalai yang terhasil mempunyai jenis DesktopPanel<DefaultPanelContent>. Jenis kandungan panel tersuai diinferens daripada penutupannya. Pemanggil tidak perlu menyatakan jenis yang mungkin rumit itu.
Satu akibat yang berguna ialah tika lalai sudah dikhususkan. Anda tidak boleh menetapkan panel dengan jenis kandungan lain kepada pemboleh ubah yang sama semata-mata kerana kedua-dua nilai memaparkan panel. Berikan cabang berasaskan pembina kepada paparan di sekelilingnya jika antara muka memerlukan persembahan alternatif.
Atribut pembina membolehkan penutupan tersuai berbilang pernyataan dalam contoh ini. Atribut itu membenarkan HStack dan Text yang berikutnya menjadi sebahagian daripada kandungan yang dibina; ia tidak menggantikan keperluan Content: View komponen itu sendiri. Apple mendokumentasikan cara pembina mengendalikan berbilang ungkapan dan kandungan bersyarat. ViewBuilder
Bilakah satu pemula pembina dengan nilai lalai boleh menggantikan lebihan beban?
Swift 5.7 melaksanakan SE-0347, yang membolehkan ungkapan lalai konkrit bagi argumen yang ditinggalkan menginfer jenis generik. Untuk komponen ini, ciri itu menyediakan alternatif yang lebih ringkas. SE-0347: Type inference from default expressions
Untuk pengkompil Swift 5.7 atau lebih baharu, gantikan pemula pembina di dalam DesktopPanel dengan cebisan berikut, dan buang keseluruhan sambungan terkekang itu. Kekalkan import, sifat tersimpan, DefaultPanelContent, body dan kod apl daripada contoh lengkap.
// Replacement initializer inside DesktopPanel.
init(
title: String,
@ViewBuilder content: () -> Content = {
DefaultPanelContent()
}
) {
self.title = title
self.content = content()
}
Kini, jika penutupan tidak disertakan, Content diinferens daripada DefaultPanelContent(). Jika penutupan dibekalkan, jenisnya pula diinferens daripada argumen tersebut. Ini ialah inferens daripada ungkapan lalai, bukannya argumen jenis lalai dalam pengisytiharan komponen.
Cadangan itu mengehadkan inferens ini: parameter generik yang terlibat mestilah muncul di satu lokasi sahaja dalam senarai parameter, dan syarat jenis sama yang berkaitan perlu mematuhi peraturannya. Di sini, Content hanya muncul dalam parameter pembina. Menggunakannya semula dalam parameter pemula yang lain boleh menyebabkan pendekatan ini tidak sah. SE-0347 detailed design
Pilih lebihan beban apabila kandungan lalai memerlukan input pemula. Contohnya, sari kata lalai yang diterbitkan daripada title boleh dibina di dalam init(title:) dan dihantar kepada pemula pembina. Ungkapan argumen lalai tidak boleh menangkap title yang dibekalkan seolah-olah argumen itu penutupan yang dicipta di dalam badan pemula tersebut.
| Keperluan komponen | Antara muka yang sesuai |
|---|---|
| Kandungan lalai tetap dan pengkompil Swift 5.7+ | Parameter pembina dengan nilai lalai lebih ringkas. |
| Kandungan lalai dibina daripada input pemula lain | Gunakan pemula terkekang dan bina nilai di dalamnya. |
| Beberapa persembahan lalai bernama | Gunakan lebihan beban pemula yang jelas dengan label dan kekangan yang nyata. |
| Jenis tersimpan yang jelas untuk pengkhususan lalai | Namakan DesktopPanel<DefaultPanelContent> atau perkenalkan alias jenis. |
Jangan cuba menyatakan antara muka ini sebagai struct DesktopPanel<Content: View = DefaultPanelContent>. Tatabahasa parameter generik Swift semasa tidak menyediakan sintaks jenis lalai itu. Pilihan yang disokong di sini ialah pemulaan terkekang dan inferens ungkapan lalai. Generic Parameters and Arguments
Kekalkan antara muka merentas versi pembina
Dokumentasi Apple semasa membezakan Xcode 26 dan lebih awal daripada Xcode 27 dan lebih baharu. Dengan rantaian alat yang lebih baharu, ViewBuilder membina kandungan tanpa mengira jenis; Apple memperkenalkan ContentBuilder, alias jenis bagi ViewBuilder, sebagai ejaan untuk tingkah laku bersatu ini. Perbezaan ini bergantung pada rantaian alat binaan, bukan sekadar sasaran penggunaan apl. ViewBuilder, ContentBuilder
Contoh ini mengekalkan @ViewBuilder demi keserasian dengan rantaian alat terdahulu. Apabila menggunakan ejaan yang lebih baharu dalam Xcode 27 atau lebih baharu, tukar atribut parameter itu kepada @ContentBuilder. Kekalkan Content: View: panel masih memerlukan kandungan yang boleh diletakkan dalam hierarki paparan.
Elakkan daripada mengeja pengkhususan lalai sebagai jenis bersarang timbunan-dan-tuple. Nota migrasi Apple mendokumentasikan perubahan daripada TupleView kepada TupleContent dalam pembina berbilang ungkapan yang boleh memecahkan kekangan yang menamakan struktur terjana tersebut. Sebaliknya, contoh ini mengekang sambungan kepada DefaultPanelContent; susun atur dalamannya kekal di sebalik body: some View. TN3211: Resolving SwiftUI source incompatibilities for State and ContentBuilder
Pembalut bernama ini juga memudahkan suntingan biasa. Menambah baris ketiga pada kandungan lalai mengubah pelaksanaannya tanpa memerlukan anda menulis semula jenis konkrit yang panjang dalam pemula kemudahan.
Diagnosis kontrak sebelum mengubah susun atur
Jika komponen gagal dikompil, kenal pasti dahulu bahagian antara muka yang kekurangan maklumat jenis. Mengubah padding atau membalut semuanya dalam timbunan lain tidak akan menyelesaikan kekurangan pengkhususan generik.
- Panggilan tanpa pembina tidak dapat menginfer
Content. Pastikaninit(title:)terkekang itu wujud, atau pemula dengan nilai lalai dikompil menggunakan Swift 5.7 atau lebih baharu. - Nilai lalai konkrit tidak dapat ditetapkan kepada
Content. Pastikan penetapan berlaku di dalam sambungan terkekang. Pematuhan kepadaViewsahaja tidak menjadikanDefaultPanelContentboleh saling menggantikan dengan setiap kemungkinanContent. - Berbilang ungkapan tersuai ditolak. Pastikan parameter penutupan mempunyai atribut pembina.
- Jenis yang dinyatakan secara jelas menolak kandungan tersuai. Anotasi
DesktopPanel<DefaultPanelContent>menetapkan jenis kandungan dengan sengaja. Buang anotasi itu apabila panggilan sepatutnya menginfer pengkhususan yang berbeza. - Naik taraf rantaian alat memecahkan kekangan berasaskan tuple. Gantikan kebergantungan pada struktur pembina terjana dengan paparan lalai bernama, atau ikuti migrasi Apple yang didokumenkan untuk kekangan tertentu itu.
Akhir sekali, pastikan pembina itu ringkas. Dalam pelaksanaan ini, pembina dijalankan setiap kali panel dimulakan; pelaksanaan ini tidak menjanjikan kerja sekali sahaja. Apple menyatakan bahawa SwiftUI mungkin memulakan semula paparan dan menasihatkan agar kerja yang ketara dielakkan dalam pemulanya. Declaring a custom view
Jadikan pemula terkekang sebagai titik permulaan yang jelas: pemula itu menampakkan pengkhususan lalai dan membolehkan kandungan tersuai diinfer dengan mudah. Gunakan pemula tunggal dengan nilai lalai apabila keperluan pengkompil dan sekatan inferensnya sesuai dengan API anda. Kedua-dua pendekatan membolehkan panel macOS mengurus rupa bersamanya, sementara pemanggil hanya membekalkan kandungan yang diperlukan.
Sumber
- Generic Parameters and Arguments — Swift.org
- Type inference from default expressions — Swift Evolution
- Declaring a custom view — Apple Developer Documentation
- ViewBuilder — Apple Developer Documentation
- ContentBuilder — Apple Developer Documentation
- WindowGroup — Apple Developer Documentation
- TN3211: Resolving SwiftUI source incompatibilities for State and ContentBuilder — Apple Developer Documentation