Seperti Martin mengatakan , jika Anda melihat dokumentasi untuk VStack
's init(alignment:spacing:content:)
, Anda dapat melihat bahwa content:
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 {
public static func buildBlock() -> EmptyView
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:
- Untuk fungsi beranotasi, blok kode yang diubah adalah implementasinya.
- Untuk properti komputasi beranotasi, blok kode yang diubah adalah pengambil.
- Untuk parameter beranotasi dari tipe fungsi, blok kode yang diubah adalah ekspresi penutupan apa pun yang diteruskan ke sana (jika ada).
Cara pembuat fungsi mengubah kode ditentukan oleh penerapan metode pembuatnya seperti buildBlock
, yang mengambil sekumpulan ekspresi dan menggabungkannya menjadi satu nilai.
Misalnya, ViewBuilder
menerapkan buildBlock
1 hingga 10 View
parameter yang sesuai, yang menggabungkan beberapa tampilan menjadi satu TupleView
:
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
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 ke buildBlock
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 dipenuhi TupleView<(Text, Text)>
.
Anda akan melihat bahwa ViewBuilder
hanya mendefinisikan buildBlock
hingga 10 parameter, jadi jika kami mencoba untuk mendefinisikan 11 subview:
var body: some View {
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")
}
Group {
Text("Hello world")
}
}
ViewBuilder
juga mengimplementasikan metode pembuat fungsi lain seperti:
extension ViewBuilder {
public static func buildEither<TrueContent, FalseContent>(first: TrueContent)
-> ConditionalContent<TrueContent, FalseContent>
where TrueContent : View, FalseContent : View
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).
@ViewBuilder
developer.apple.com/documentation/swiftui/viewbuilder .