ดังที่มาร์ตินกล่าวหากคุณดูเอกสารประกอบVStack
ของinit(alignment:spacing:content:)
คุณจะเห็นว่าcontent:
พารามิเตอร์มีแอตทริบิวต์@ViewBuilder
:
init(alignment: HorizontalAlignment = .center, spacing: Length? = nil,
@ViewBuilder content: () -> Content)
แอตทริบิวต์นี้อ้างถึงViewBuilder
ประเภทซึ่งหากคุณดูอินเทอร์เฟซที่สร้างขึ้นดูเหมือนว่า:
@_functionBuilder public struct ViewBuilder {
public static func buildBlock() -> EmptyView
public static func buildBlock(_ content: Content) -> Content
where Content : View
}
@_functionBuilder
แอตทริบิวต์เป็นส่วนหนึ่งของคุณลักษณะที่ไม่เป็นทางการเรียกว่า " ผู้สร้างฟังก์ชั่น " ซึ่งได้รับเสียงแหลมวิวัฒนาการของสวิฟท์ที่นี่และดำเนินการมาเป็นพิเศษสำหรับรุ่นของสวิฟท์ที่มาพร้อมกับ Xcode 11 ปล่อยให้มันถูกนำมาใช้ใน SwiftUI
การทำเครื่องหมายประเภท@_functionBuilder
ช่วยให้สามารถใช้เป็นแอตทริบิวต์ที่กำหนดเองในการประกาศต่างๆเช่นฟังก์ชันคุณสมบัติที่คำนวณและในกรณีนี้พารามิเตอร์ของประเภทฟังก์ชัน การประกาศคำอธิบายประกอบดังกล่าวใช้ตัวสร้างฟังก์ชันเพื่อแปลงบล็อกโค้ด:
- สำหรับฟังก์ชันที่มีคำอธิบายประกอบบล็อกของโค้ดที่ได้รับการแปลงคือการนำไปใช้งาน
- สำหรับคุณสมบัติที่คำนวณด้วยคำอธิบายประกอบบล็อกของโค้ดที่ถูกแปลงคือ getter
- สำหรับพารามิเตอร์ที่มีคำอธิบายประกอบของประเภทฟังก์ชันบล็อกของโค้ดที่ถูกแปลงคือนิพจน์การปิดใด ๆ ที่ส่งผ่านไปยัง (ถ้ามี)
วิธีที่ตัวสร้างฟังก์ชันแปลงโค้ดถูกกำหนดโดยการใช้งานเมธอดตัวสร้างเช่นbuildBlock
ซึ่งใช้ชุดของนิพจน์และรวมเข้าเป็นค่าเดียว
ตัวอย่างเช่นViewBuilder
การใช้พารามิเตอร์ที่สอดคล้องกันbuildBlock
1 ถึง 10 รายการView
โดยรวมหลายมุมมองไว้ในรายการเดียว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
}
สิ่งนี้ช่วยให้ชุดของนิพจน์มุมมองภายในการปิดที่ส่งผ่านไปยังVStack
ผู้เริ่มต้นของสามารถเปลี่ยนเป็นการเรียกไปbuildBlock
ที่ใช้อาร์กิวเมนต์จำนวนเท่ากัน ตัวอย่างเช่น:
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
Text("Hello, World")
Text("Hello World!")
}
}
}
ถูกเปลี่ยนเป็นการเรียกร้องให้buildBlock(_:_:)
:
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
ViewBuilder.buildBlock(Text("Hello, World"), Text("Hello World!"))
}
}
}
ส่งผลให้ในประเภทผลขุ่น ถูกความพึงพอใจโดยsome View
TupleView<(Text, Text)>
คุณจะทราบว่าViewBuilder
กำหนดbuildBlock
พารามิเตอร์ได้สูงสุด 10 รายการเท่านั้นดังนั้นหากเราพยายามกำหนดมุมมองย่อย 11 รายการ:
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!")
}
}
เราได้รับข้อผิดพลาดของคอมไพเลอร์เนื่องจากไม่มีวิธีการสร้างในการจัดการบล็อกโค้ดนี้ (โปรดทราบว่าเนื่องจากคุณลักษณะนี้ยังคงอยู่ระหว่างดำเนินการข้อความแสดงข้อผิดพลาดที่อยู่รอบ ๆ จึงไม่เป็นประโยชน์)
ในความเป็นจริงฉันไม่เชื่อว่าผู้คนจะพบกับข้อ จำกัด นี้บ่อยครั้งเช่นตัวอย่างข้างต้นจะได้รับการบริการที่ดีกว่าโดยใช้ForEach
มุมมองแทน
var body: some View {
VStack(alignment: .leading) {
ForEach(0 ..< 20) { i in
Text("Hello world \(i)")
}
}
}
อย่างไรก็ตามหากคุณต้องการมุมมองที่กำหนดแบบคงที่มากกว่า 10 มุมมองคุณสามารถแก้ไขข้อ จำกัด นี้ได้อย่างง่ายดายโดยใช้Group
มุมมอง:
var body: some View {
VStack(alignment: .leading) {
Group {
Text("Hello world")
}
Group {
Text("Hello world")
}
}
ViewBuilder
ยังใช้วิธีการสร้างฟังก์ชันอื่น ๆ เช่น:
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
}
สิ่งนี้ทำให้สามารถจัดการกับคำสั่ง if:
var body: some View {
VStack(alignment: .leading) {
if .random() {
Text("Hello World!")
} else {
Text("Goodbye World!")
}
Text("Something else")
}
}
ซึ่งเปลี่ยนเป็น:
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")
)
}
}
(ส่งเสียงเรียก 1 อาร์กิวเมนต์ซ้ำซ้อนเพื่อViewBuilder.buildBlock
ความชัดเจน)
@ViewBuilder