@ViewBuilder คือคำอธิบายประกอบ result builder ใน SwiftUI ที่ออกแบบมาสำหรับการสร้างลำดับชั้น View แบบประกาศ ตาม Apple Developer Documentation, 2024 @ViewBuilder จะแปลงบล็อกโค้ดที่มีนิพจน์หลายรายการและตรรกะแบบมีเงื่อนไขให้เป็นชนิด View เดียวที่คอมไพเลอร์ Swift สามารถเข้าใจได้ หากไม่มีคำอธิบายประกอบนี้ ก็จะเป็นไปไม่ได้ที่จะใช้ไวยากรณ์ประกาศของ SwiftUI ที่คุ้นเคยกับ if/else และองค์ประกอบหลายรายการใน body
ประเด็นสำคัญ
@ViewBuilder คือคำอธิบายประกอบที่ใช้รูปแบบ result builder (SE-0289) ซึ่งช่วยให้ SwiftUI รวมหลาย Views เป็นองค์ประกอบเดียวโดยใช้ไวยากรณ์แบบประกาศ มันจะห่อนิพจน์หลายรายการ โครงสร้างแบบมีเงื่อนไข และค่าเลือกได้เป็นชนิดที่สอดคล้องกันโดยอัตโนมัติ: TupleView, ConditionalContent, OptionalContent
ก่อนที่ result builders จะมีขึ้น นักพัฒนาต้องห่อองค์ประกอบด้วยตนเองใน VStack หรือ HStack และใช้ตัวดำเนินการ ternary หรือเมธอด factory สำหรับตรรกะแบบมีเงื่อนไข @ViewBuilder ทำให้ไวยากรณ์ของ SwiftUI กระชับและอ่านง่าย ช่วยให้เขียนโค้ดที่ดูเหมือน Swift ปกติกับ if/else และลูป
ตาม Swift Evolution SE-0289 result builders เป็นกลไกทั่วไปที่ไม่ผูกติดกับ SwiftUI @ViewBuilder เป็นหนึ่งในการใช้งานของกลไกนี้ พร้อมกับ @StringBuilder สำหรับการสร้างสตริงและการใช้งานไลบรารีสำหรับ DSL อื่นๆ ใน SwiftUI @ViewBuilder ไม่ได้ใช้เพียงสำหรับ body เท่านั้น แต่ยังใช้สำหรับพารามิเตอร์ closure ของคอนเทนเนอร์ (VStack, HStack, ZStack, List) ด้วย
ใน UIKit เชิงคำสั่ง คุณสร้าง UIView อย่างชัดเจน กำหนดค่าคุณสมบัติของมัน และเพิ่มลงในลำดับชั้นผ่าน addSubview ใน SwiftUI กับ @ViewBuilder คุณอธิบายอย่างประกาศว่าควรแสดง Views ใดบ้าง และ SwiftUI จะจัดการการสร้าง การอัปเดต และการลบองค์ประกอบตามการเปลี่ยนแปลงสถานะ
Result builder คือกลไกของ Swift ที่แปลงลำดับของนิพจน์เป็นค่าเชิงประกอบเดียวผ่านเมธอดสแตติก buildBlock, buildOptional, buildEither และอื่นๆ เมื่อคอมไพเลอร์เห็นคำอธิบายประกอบ @ViewBuilder มันจะใช้เมธอดเหล่านี้กับบล็อกโค้ดโดยอัตโนมัติระหว่างการคอมไพล์
@resultBuilder
struct ViewBuilder {
static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)>
static func buildIf<C>(_ c: C?) -> C?
static func buildEither<T, F>(first: T) -> ConditionalContent<T, F>
static func buildEither<T, F>(second: F) -> ConditionalContent<T, F>
}
buildBlock รับนิพจน์ 1 ถึง 10 รายการและส่งคืน TupleView แต่ละอาริตี้ (จำนวนนิพจน์) มี overload buildBlock ของตัวเอง: จาก buildBlock
buildEither (first/second) จัดการกับโครงสร้าง if/else แต่ละสาขาจะถูกส่งไปยังเมธอดที่เกี่ยวข้อง และผลลัพธ์จะถูกห่อใน ConditionalContent — ชนิดที่ซ่อนชนิดสาขาเฉพาะและให้อินเทอร์เฟซแบบรวมสำหรับ SwiftUI
ใน SwiftUI คุณสมบัติ body ถูกอธิบายประกอบด้วย @ViewBuilder โดยนัยอยู่แล้ว — คุณไม่เห็นคำอธิบายประกอบนี้ในโค้ด แต่คอมไพเลอร์จะใช้มันโดยอัตโนมัติ อย่างไรก็ตาม สำหรับคุณสมบัติที่กำหนดเองที่ส่งคืนหลาย Views หรือสำหรับพารามิเตอร์ closure คำอธิบายประกอบต้องระบุอย่างชัดเจน
ข้อจำกัด 1 — 10 องค์ประกอบในบล็อก นี่คือข้อจำกัดที่รู้จักกันดีที่สุดของ @ViewBuilder หากคุณต้องการแสดงมากกว่า 10 องค์ประกอบในระดับเดียวกัน คอมไพเลอร์จะแสดงข้อผิดพลาด วิธีแก้ไขรวมถึง Group, ForEach, List หรือการแบ่งเป็นคอมโพเนนต์ย่อย Group ไม่เพิ่มการซ้อนด้วยภาพ แต่แต่ละ Group นับเป็นหนึ่งองค์ประกอบ
struct ManyElementsView: View {
var body: some View {
Group {
Text("1"); Text("2"); Text("3")
Text("4"); Text("5"); Text("6")
Text("7"); Text("8"); Text("9")
}
Group {
Text("10"); Text("11"); Text("12")
}
}
}
ข้อจำกัด 2 — ขาดการสนับสนุนโครงสร้างบางอย่าง @ViewBuilder ไม่รองรับ do/catch, guard, for-in (ไม่มี ForEach) และโครงสร้างควบคุมการไหลอื่นๆ สำหรับลูป ให้ใช้ ForEach กับข้อมูลที่ระบุตัวตนได้ สำหรับการจัดการข้อผิดพลาด ให้ใช้ Views แยกต่างหากที่รับ Result หรือค่าเลือกได้
ข้อจำกัด 3 — ความซับซ้อนในการดีบัก เมื่อเกิดข้อผิดพลาดใน @ViewBuilder คอมไพเลอร์จะสร้างข้อความที่ยืดยาวซึ่งยากต่อการหาสาเหตุที่แท้จริง ปัญหาทั่วไป: ชนิดไม่ตรงกันในสาขา if/else, เกินขีดจำกัด 10 องค์ประกอบ หรือขาด overload buildBlock ที่จำเป็น
รูปแบบ 1: การแสดงผลแบบมีเงื่อนไขผ่าน if/else กรณีการใช้งานที่พบบ่อยที่สุดของ @ViewBuilder ช่วยให้แสดง Views ต่างกันตามสถานะโดยไม่ต้องใช้ตัวดำเนินการ ternary หรือเมธอด factory
struct StatusView: View {
var status: LoadStatus
@ViewBuilder
var body: some View {
switch status {
case .loading:
ProgressView("Loading...")
case .loaded(let data):
DataView(data: data)
case .error(let message):
ErrorView(message: message)
}
}
}
รูปแบบ 2: @ViewBuilder ในพารามิเตอร์ฟังก์ชันและตัวเริ่มต้น ใช้สำหรับสร้างคอนเทนเนอร์ที่ใช้ซ้ำได้ซึ่งรับ Views ลูกผ่าน closure นี่คือรูปแบบมาตรฐานสำหรับไลบรารีและคอมโพเนนต์ UI
struct SectionCard<Content: View>: View {
let title: String
@ViewBuilder let content: Content
var body: some View {
VStack(alignment: .leading) {
Text(title).font(.headline)
content
}
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(12)
}
}
รูปแบบ 3: การประกอบกับ ForEach @ViewBuilder ทำงานอย่างถูกต้องกับ ForEach ช่วยให้สร้างองค์ประกอบแบบไดนามิกจากอาร์เรย์ข้อมูล แต่ละองค์ประกอบของ ForEach นับเป็นหนึ่งนิพจน์ในบริบทของ @ViewBuilder
ViewBuilder แบบกำหนดเอง คือฟังก์ชันหรือคุณสมบัติที่ผู้ใช้กำหนดซึ่งอธิบายประกอบด้วย @ViewBuilder และส่งคืน some View ฟังก์ชันดังกล่าวช่วยให้ห่อหุ้มตรรกะการแสดงผลที่ซับซ้อนและนำกลับมาใช้ใหม่ในส่วนต่างๆ ของแอปพลิเคชัน
struct FormRow<Content: View>: View {
let label: String
@ViewBuilder let content: Content
var body: some View {
HStack {
Text(label)
.frame(width: 120, alignment: .trailing)
content
}
}
}
// การใช้งาน:
FormRow(label: "Name") {
TextField("Enter name", text: $name)
}
FormRow(label: "Gender") {
Picker("Select", selection: $gender) {
Text("ชาย").tag(Gender.male)
Text("หญิง").tag(Gender.female)
}
}
กฎสำคัญ: ฟังก์ชันที่กำหนดเองกับ @ViewBuilder ต้องส่งคืน some View ไม่ใช่ชนิดที่เป็นรูปธรรมหรือโปรโตคอล View ชนิดทึบแสงเท่านั้นที่ช่วยให้ซ่อนการใช้งานที่เป็นรูปธรรมในขณะที่ยังคงความยืดหยุ่นในการประกอบ
ประสิทธิภาพ: ฟังก์ชัน @ViewBuilder แบบกำหนดเองไม่เพิ่มค่าใช้จ่ายเมื่อเทียบกับโค้ดโดยตรงใน body คอมไพเลอร์ทำ inline การเรียกและปรับโค้ดผลลัพธ์ให้เหมาะสม การแบ่ง body เป็นฟังก์ชัน @ViewBuilder ช่วยเพิ่มความสามารถในการอ่านโดยไม่เสียสละประสิทธิภาพ
คำถามที่พบบ่อย
@ViewBuilder คือคำอธิบายประกอบ result builder ที่แปลงบล็อกโค้ดที่มีนิพจน์หลายรายการและเงื่อนไขเป็นชนิด View เดียว ช่วยให้ใช้ไวยากรณ์ Swift ที่คุ้นเคย (if/else, switch, นิพจน์เลือกได้) ภายใน UI แบบประกาศของ SwiftUI
ข้อจำกัดเกิดจากการใช้งาน buildBlock — มี overload แยกของเมธอดสำหรับแต่ละอาริตี้ตั้งแต่ 1 ถึง 10 Swift ไม่รองรับ generics แบบแปรผัน ดังนั้นจำนวน overload จึงคงที่ เพื่อหลีกเลี่ยง ให้ใช้ Group, ForEach หรือคอมโพเนนต์ย่อย
ไม่ โปรโตคอล View ใช้งานโดยนัย @ViewBuilder กับคุณสมบัติ body อย่างไรก็ตาม สำหรับคุณสมบัติที่กำหนดเอง เมธอด และพารามิเตอร์ closure ที่ส่งคืนหลาย Views คำอธิบายประกอบต้องระบุอย่างชัดเจน หากไม่มี คอมไพเลอร์จะไม่สามารถจัดการนิพจน์หลายรายการได้
สำหรับนิพจน์เลือกได้ จะใช้เมธอด buildIf ซึ่งรับ View แบบเลือกได้และส่งคืนหากมีค่า หากเป็น nil buildIf จะส่งคืน nil และองค์ประกอบจะไม่แสดง ซึ่งช่วยให้ใช้ if let ภายใน body
ได้ ตั้งแต่ Swift 5.9 @ViewBuilder รองรับ switch ผ่านเมธอด buildExpression คอมไพเลอร์จะแปลงแต่ละสาขา case เป็นการเรียก buildEither ที่สอดคล้องกัน การรองรับ switch ทำให้โค้ดอ่านง่ายขึ้นเมื่อเทียบกับโครงสร้าง if/else ที่ซ้อนกัน
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม