Sepertinya SwiftUI
kerangka kerja baru Apple menggunakan jenis sintaks baru yang secara efektif membangun tupel, tetapi memiliki sintaks lain:
var body: some View {
VStack(alignment: .leading) {
Text("Hello, World") // No comma, no separator ?!
Text("Hello World!")
}
}
Mencoba untuk mengatasi apa sebenarnya sintaks ini , saya menemukan bahwa VStack
penginisialisasi yang digunakan di sini mengambil penutupan tipe () -> Content
sebagai parameter kedua, di mana parameter Content
generik yang sesuai dengan View
yang disimpulkan melalui penutupan. Untuk mengetahui jenis apa Content
yang disimpulkan, saya mengubah kode sedikit, mempertahankan fungsinya:
var body: some View {
let test = VStack(alignment: .leading) {
Text("Hello, World")
Text("Hello World!")
}
return test
}
Dengan ini, test
mengungkapkan dirinya menjadi tipe VStack<TupleView<(Text, Text)>>
, artinyaContent
dari tipe TupleView<Text, Text>
. Mendongak TupleView
, saya menemukan itu adalah jenis pembungkus yang berasal dari SwiftUI
dirinya sendiri yang hanya dapat diinisialisasi dengan meneruskan tupel yang harus dibungkus.
Pertanyaan
Sekarang saya bertanya-tanya bagaimana dua Text
contoh dalam contoh ini diubah menjadi TupleView<(Text, Text)>
. Apakah ini diretas SwiftUI
dan karena itu sintaks Swift reguler tidak valid? TupleView
menjadi SwiftUI
tipe mendukung asumsi ini. Atau apakah ini sintaks Swift yang valid? Jika ya, bagaimana cara menggunakannya di luar SwiftUI
?
@ViewBuilder
developer.apple.com/documentation/swiftui/viewbuilder .Jawaban:
Seperti Martin mengatakan , jika Anda melihat dokumentasi untuk
VStack
'sinit(alignment:spacing:content:)
, Anda dapat melihat bahwacontent:
parameter memiliki atribut@ViewBuilder
:init(alignment: HorizontalAlignment = .center, spacing: Length? = nil, @ViewBuilder content: () -> Content)
Atribut ini mengacu pada
ViewBuilder
tipe, yang jika Anda melihat antarmuka yang dihasilkan, terlihat seperti:@_functionBuilder public struct ViewBuilder { /// Builds an empty view from an block containing no statements, `{ }`. public static func buildBlock() -> EmptyView /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) /// through unmodified. public static func buildBlock(_ content: Content) -> Content where Content : View }
The
@_functionBuilder
atribut adalah bagian dari fitur tidak resmi disebut " fungsi pembangun ", yang telah bernada tentang evolusi Swift di sini , dan dilaksanakan khusus untuk versi Swift yang dikirimkan dengan Xcode 11, yang memungkinkan untuk digunakan dalam SwiftUI.Menandai tipe
@_functionBuilder
memungkinkannya untuk digunakan sebagai atribut khusus pada berbagai deklarasi seperti fungsi, properti yang dihitung, dan, dalam hal ini, parameter tipe fungsi. Deklarasi beranotasi seperti itu menggunakan pembuat fungsi untuk mengubah blok kode:Cara pembuat fungsi mengubah kode ditentukan oleh penerapan metode pembuatnya seperti
buildBlock
, yang mengambil sekumpulan ekspresi dan menggabungkannya menjadi satu nilai.Misalnya,
ViewBuilder
menerapkanbuildBlock
1 hingga 10View
parameter yang sesuai, yang menggabungkan beberapa tampilan menjadi satuTupleView
:@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension ViewBuilder { /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) /// through unmodified. public static func buildBlock<Content>(_ content: Content) -> Content where Content : View public static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)> where C0 : View, C1 : View public static func buildBlock<C0, C1, C2>(_ c0: C0, _ c1: C1, _ c2: C2) -> TupleView<(C0, C1, C2)> where C0 : View, C1 : View, C2 : View // ... }
Hal ini memungkinkan sekumpulan ekspresi tampilan dalam closure yang diteruskan ke
VStack
inisialisasi untuk diubah menjadi panggilan kebuildBlock
yang menggunakan jumlah argumen yang sama. Sebagai contoh:struct ContentView : View { var body: some View { VStack(alignment: .leading) { Text("Hello, World") Text("Hello World!") } } }
diubah menjadi panggilan ke
buildBlock(_:_:)
:struct ContentView : View { var body: some View { VStack(alignment: .leading) { ViewBuilder.buildBlock(Text("Hello, World"), Text("Hello World!")) } } }
menghasilkan jenis hasil buram
some View
yang dipenuhiTupleView<(Text, Text)>
.Anda akan melihat bahwa
ViewBuilder
hanya mendefinisikanbuildBlock
hingga 10 parameter, jadi jika kami mencoba untuk mendefinisikan 11 subview:var body: some View { // error: Static member 'leading' cannot be used on instance of // type 'HorizontalAlignment' VStack(alignment: .leading) { Text("Hello, World") Text("Hello World!") Text("Hello World!") Text("Hello World!") Text("Hello World!") Text("Hello World!") Text("Hello World!") Text("Hello World!") Text("Hello World!") Text("Hello World!") Text("Hello World!") } }
kami mendapatkan kesalahan kompiler, karena tidak ada metode pembuat untuk menangani blok kode ini (perhatikan bahwa karena fitur ini masih dalam proses, pesan kesalahan di sekitarnya tidak akan membantu).
Pada kenyataannya, saya tidak yakin orang-orang akan sering mengalami pembatasan ini, misalnya contoh di atas akan lebih baik disajikan menggunakan
ForEach
tampilan:var body: some View { VStack(alignment: .leading) { ForEach(0 ..< 20) { i in Text("Hello world \(i)") } } }
Namun, jika Anda memang membutuhkan lebih dari 10 tampilan yang ditentukan secara statis, Anda dapat dengan mudah mengatasi batasan ini menggunakan
Group
tampilan:var body: some View { VStack(alignment: .leading) { Group { Text("Hello world") // ... // up to 10 views } Group { Text("Hello world") // ... // up to 10 more views } // ... }
ViewBuilder
juga mengimplementasikan metode pembuat fungsi lain seperti:extension ViewBuilder { /// Provides support for "if" statements in multi-statement closures, producing /// ConditionalContent for the "then" branch. public static func buildEither<TrueContent, FalseContent>(first: TrueContent) -> ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View /// Provides support for "if-else" statements in multi-statement closures, /// producing ConditionalContent for the "else" branch. public static func buildEither<TrueContent, FalseContent>(second: FalseContent) -> ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View }
Ini memberinya kemampuan untuk menangani pernyataan if:
var body: some View { VStack(alignment: .leading) { if .random() { Text("Hello World!") } else { Text("Goodbye World!") } Text("Something else") } }
yang diubah menjadi:
var body: some View { VStack(alignment: .leading) { ViewBuilder.buildBlock( .random() ? ViewBuilder.buildEither(first: Text("Hello World!")) : ViewBuilder.buildEither(second: Text("Goodbye World!")), Text("Something else") ) } }
(memancarkan panggilan 1-argumen yang berlebihan ke
ViewBuilder.buildBlock
untuk kejelasan).sumber
ViewBuilder
hanya menentukanbuildBlock
hingga 10 parameter - apakah itu berartivar body: some View
tidak boleh memiliki lebih dari 11 subview?ForEach
tampilan. Namun Anda dapat menggunakanGroup
tampilan untuk mengatasi batasan ini, saya telah mengedit jawaban saya untuk menunjukkannya.Hal serupa dijelaskan di What's New in Swift WWDC video di bagian tentang DSLs (dimulai pada ~ 31: 15). Atribut diinterpretasikan oleh kompilator dan diterjemahkan ke dalam kode terkait:
sumber