Isytiharkan kandungan yang dibekalkan pemanggil sebagai (Input) -> Content, dengan Content dihadkan kepada View. Ini membolehkan komponen macOS membekalkan nilai bertip sementara pemanggil memilih cara persembahannya. Kontrak ini mewajibkan input tersebut diterima; ia tidak boleh mewajibkan input itu digunakan.

Tutorial ini membina pratonton sampel warna desktop yang ringkas. Komponen boleh guna semula memiliki kawalan saiz dan membekalkan konfigurasi sintetik kepada penutupan pemaparan pemanggil. Pemanggil melukis sampel warna itu tanpa mengurus keadaan dalaman komponen.

Antara muka apakah yang menyatakan keperluan ini?

Gunakan parameter generik bernama untuk paparan yang dikembalikan dan jenis konkrit untuk input komponen. Dalam contoh di bawah, tandatangan pentingnya ialah @ViewBuilder content: @escaping (SwatchInput) -> Content, dalam pengisytiharan yang dihadkan oleh Content: View.

Input dan output mempunyai tugas yang berasingan. SwatchInput mentakrifkan nilai yang dibekalkan komponen. Content mewakili jenis paparan konkrit yang dihasilkan oleh penutupan setiap pemanggil. Parameter generik bernama membolehkan pengisytiharan menyatakan dan menggunakan semula hubungan jenis ini, seperti yang diterangkan dalam bab Generik Swift.

Apple menggunakan corak antara muka ini dalam GeometryReader: pemulanya menerima penutupan jenis (GeometryProxy) -> Content. Bekas itu membekalkan proksi, dan pemanggil membina kandungan daripadanya. Ini menjadi contoh rujukan yang berguna bagi komponen tersuai yang membekalkan konfigurasinya sendiri. Lihat dokumentasi Apple untuk GeometryReader dan init(content:).

Pilih kontrak mengikut tugas sebenar:

Keperluan Antara muka yang sesuai
Pemanggil membekalkan kandungan tanpa input komponen () -> Content
Komponen membekalkan konfigurasi tertentu (SwatchInput) -> Content
Bekas menerima nilai yang jenisnya berbeza-beza antara penggunaan Parameter generik Input bernama, dengan penutupan (Input) -> Content yang sepadan
Pengkompil mesti membuktikan bahawa pemanggil menggunakan konfigurasi Tandatangan penutupan tidak menyatakan keperluan itu

Pilihan ini berpunca daripada jenis fungsi Swift: jenis parameter dan jenis pulangannya menerangkan perkara yang boleh dihantar dan dipulangkan. Jenis tersebut tidak menerangkan cara pelaksanaan mesti menggunakan argumennya. Lihat bahagian Jenis Fungsi dalam rujukan bahasa Swift.

Bina pratonton sampel warna macOS yang lengkap

Gunakan sasaran app macOS dengan SwiftUI. Contoh ini menggunakan ejaan @ViewBuilder yang telah mantap dan ditujukan untuk Swift 5.7 atau lebih baharu, dengan sasaran penggunaan macOS 12 atau lebih baharu. Contoh ini mengelakkan makro pratonton dan nama pembina kandungan yang lebih baharu. Swift dan SDK Apple tidak tersedia dalam persekitaran penyelidikan, jadi program ini telah diperiksa tetapi tidak dikompil atau dijalankan di sini.

Dalam projek app SwiftUI Xcode, gantikan pengisytiharan app yang dijana dengan program lengkap berikut. Buang pengisytiharan ContentView yang dijana jika ia akan menduplikasi pengisytiharan ini. Pastikan hanya satu titik masuk app ditandai dengan @main. Bantuan Xcode Apple menerangkan cara memulakan projek SwiftUI atau menambah fail SwiftUI pada projek sedia ada.

import SwiftUI

struct SwatchInput {
    let title: String
    let side: CGFloat
    let color: Color
}

@MainActor
struct SwatchPanel<Content: View>: View {
    @State private var isLarge = false
    private let content: (SwatchInput) -> Content

    init(
        @ViewBuilder content: @escaping (SwatchInput) -> Content
    ) {
        self.content = content
    }

    private var input: SwatchInput {
        SwatchInput(
            title: "Sample A",
            side: isLarge ? 120 : 64,
            color: .blue
        )
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Toggle("Large sample", isOn: $isLarge)
            content(input)
        }
        .padding(24)
    }
}

@MainActor
struct ContentView: View {
    var body: some View {
        SwatchPanel { input in
            Text(input.title)
                .font(.headline)

            RoundedRectangle(cornerRadius: 12)
                .fill(input.color)
                .frame(width: input.side, height: input.side)

            if input.side == 120 {
                Text("Large preview")
                    .font(.caption)
            }
        }
        .frame(width: 300, height: 280, alignment: .topLeading)
    }
}

@main
@MainActor
struct TypedContentDemoApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Paparan awal yang dijangka mengandungi togol Large sample, tajuk Sample A, dan segi empat sama biru bersudut bulat dengan panjang sisi 64 poin. Menghidupkan togol itu menukar panjang sisi yang dibekalkan kepada 120 poin dan menambah Large preview. Saiz dan label tersebut ialah nilai contoh setempat, bukannya hasil pengukuran.

Komponen memiliki isLarge. input terhitungnya membungkus pilihan semasa, dan content(input) menghantar bungkusan itu kepada pemanggil. SwiftUI mengurus storan keadaan dan mengemas kini bahagian hierarki paparan yang bergantung pada nilai yang berubah, seperti yang didokumentasikan di bawah State.

Pemanggil memiliki persembahan: tajuk, bentuk dan kapsyen pilihan. Pemanggil boleh menggantikan susun atur itu dengan susunan lain sambil mengekalkan input bertip yang sama. Panel dan pemanggilnya masing-masing melaksanakan body untuk menggubah antara muka mereka, selaras dengan model View yang didokumentasikan Apple.

Mengapakah anotasi pembina dan escaping diperlukan?

@ViewBuilder membolehkan pemanggil menulis beberapa ungkapan paparan dan cabang bersyarat yang disokong dalam satu penutupan. Dalam contoh ini, pemanggil membekalkan dua paparan tanpa syarat dan satu kapsyen bersyarat. Pembina menggabungkan kandungan itu menjadi hasil yang memenuhi kekangan pulangan komponen. Apple mendokumentasikan berbilang paparan anak dan pembinaan bersyarat di bawah ViewBuilder.

Pembina itu tidak membekalkan SwatchInput. Parameter penutupan biasa mengisytiharkan input, dan content(input) membekalkan argumen sebenar. Jika pemula menerima () -> Content, penambahan anotasi pembina tidak akan menukarkannya menjadi penutupan yang menerima konfigurasi.

@escaping diperlukan kerana pemula menyimpan penutupan itu dalam sifat untuk dipanggil kemudian daripada body. Bab Closures Swift menerangkan bahawa penutupan dianggap escaping apabila ia boleh dipanggil selepas fungsi penerimanya dipulangkan.

Bagi komponen yang konfigurasinya sudah muktamad semasa pemulaan, reka bentuk lain ialah memanggil pembina yang tidak escaping serta-merta dan menyimpan nilai Content yang terhasil. Ini mengubah masa konfigurasi digunakan. Di sini, mengekalkan penutupan menjadikan aliran yang dimaksudkan jelas: terbitkan input semasa daripada keadaan komponen, kemudian bina kandungan menggunakan input itu.

Kekalkan sempadan pembina Xcode

Dokumentasi semasa Apple membezakan Xcode 26 dan lebih awal daripada Xcode 27 dan lebih baharu. Untuk alatan yang lebih awal, dokumentasi itu menerangkan ViewBuilder sebagai pembina paparan. Dengan Xcode 27 dan lebih baharu, dokumentasi itu menerangkan pembinaan tanpa mengira jenis dan mengesyorkan alias ContentBuilder untuk menunjukkan tingkah laku yang lebih luas itu. Lihat ViewBuilder.

Apabila menyelenggara pangkalan kod Xcode 27 atau lebih baharu, pemula boleh menggunakan @ContentBuilder sebagai ganti. Apple mendokumentasikannya sebagai alias jenis untuk ViewBuilder, dengan pematuhan dikuatkuasakan melalui jenis kandungan yang dihasilkannya. Kekalkan Content: View pada komponen ini: outputnya masih ditujukan untuk hierarki paparan. Jangan anggap ejaan yang lebih baharu itu wujud dalam SDK lama hanya kerana dokumentasi semasa menyenaraikan sasaran penggunaan yang lebih lama. Lihat ContentBuilder.

Mengapakah some View tidak menyelesaikan setiap bahagian kontrak ini?

some View sesuai untuk sifat body dalam contoh ini. Ia menyembunyikan jenis hasil konkrit sambil mengekalkan janji bahawa hasil itu ialah paparan. Sebaliknya, penutupan tersimpan menggunakan parameter Content bernama supaya komponen boleh merujuk jenis hasil yang sama dalam sifat dan pemulanya.

Swift 5.7 memperkenalkan pengisytiharan parameter legap melalui SE-0341. Parameter langsung seperti _ value: some View memperkenalkan parameter generik tanpa nama yang jenis konkritnya ditentukan oleh pemanggil. Ini tidak bermaksud pelaksanaan fungsi boleh memilih paparan sewenang-wenangnya dan menghantarnya ke mana-mana tempat yang menjangkakan jenis tanpa nama itu. Lihat Pengisytiharan Parameter Legap.

Khususnya, parameter panggil balik yang ditulis sebagai (some View) -> Content berada di luar kedudukan parameter legap yang disokong dan didokumentasikan oleh SE-0341. Cadangan itu secara jelas melarang jenis legap dalam kedudukan input jenis fungsi panggil balik. Perbincangannya tentang kemungkinan mendayakan sintaks itu terdapat di bawah hala tuju masa hadapan, bukannya tingkah laku yang telah dilaksanakan. Rujukan Types semasa mengekalkan sekatan itu.

Untuk tugas desktop ini, cara yang disokong adalah mudah: jadikan nilai yang dibekalkan komponen sebagai jenis konfigurasi konkrit. Jika bekas boleh guna semula yang lain perlu mengekalkan jenis input yang dibekalkan pemanggil, namakan jenis itu dengan parameter generik dan bawa secara konsisten melalui nilai tersimpan dan panggil balik. Kedua-dua reka bentuk tidak memerlukan input panggil balik legap.

Apakah yang sebenarnya dijamin oleh pengkompil?

Tandatangan itu menjamin bentuk yang boleh dipanggil: satu argumen SwatchInput dan hasil yang memenuhi Content: View. Tandatangan itu juga memberikan jenis terbitan kepada parameter penutupan, jadi pemanggil boleh mengakses input.title, input.side dan input.color tanpa mengulang anotasi jenis. Swift mendokumentasikan inferens daripada konteks penutupan ini dalam Closures.

Tandatangan itu tidak dapat menjamin bahawa sifat tersebut mempengaruhi paparan. Contohnya, ini ialah ungkapan gantian yang sah di dalam ContentView.body; ungkapan ini sengaja menerima dan mengabaikan konfigurasi:

SwatchPanel { _ in
    Text("Fixed preview")
}

Sempadan itu penting semasa menulis keperluan komponen. “Kandungan pemanggil menerima SwatchInput” ialah kontrak jenis. “Kandungan pemanggil memaparkan saiz yang dibekalkan” ialah keperluan tingkah laku. Jika paparan saiz itu penting bagi komponen, letakkan pemaparan tersebut di dalam komponen dan dedahkan ruang penyesuaian yang lebih kecil.

Gunakan semakan berikut untuk mendiagnosis masalah integrasi. Ini ialah langkah pemeriksaan yang diterbitkan daripada antara muka, bukannya hasil ujian pengkompil yang dilaporkan:

Gejala Semakan
Argumen tiada apabila memanggil content Hantarkan konfigurasi: content(input).
Diagnostik penutupan escaping dalam pemula Kekalkan @escaping apabila menyimpan panggil balik.
Beberapa paparan sebaris gagal dalam pemanggil Kekalkan anotasi pembina yang sesuai pada parameter pemula.
Panggil balik yang ditaip secara jelas ditolak Semak bahawa inputnya ialah SwatchInput, bukannya jenis yang tidak berkaitan seperti String.
Pratonton mengabaikan togol saiz Semak sama ada pemaparan membaca input.side.
Komponen terus memaparkan konfigurasi awal Semak sama ada kandungan dibina sekali dan disimpan, bukannya memanggil panggil balik dengan input semasa.

Pastikan kontrak sekecil yang dibenarkan oleh tugas. Konfigurasi konkrit dan hasil paparan generik memberikan panel sampel warna ini sempadan yang jelas: komponen membekalkan nilai semasa, pemanggil menerimanya dengan maklumat jenis penuh, dan persembahan yang dipilih kekal mudah diganti.

Sumber