@ViewBuilder là một chú thích result builder trong SwiftUI được thiết kế để xây dựng hệ phân cấp View theo cách khai báo. Theo Apple Developer Documentation, 2024, @ViewBuilder biến đổi một khối mã với nhiều biểu thức và logic điều kiện thành một kiểu View duy nhất mà trình biên dịch Swift có thể hiểu được. Nếu không có chú thích này, sẽ không thể sử dụng cú pháp SwiftUI khai báo quen thuộc với if/else và nhiều phần tử trong body.
Những điểm chính
@ViewBuilder là một chú thích triển khai mẫu result builder (SE-0289), cho phép SwiftUI kết hợp nhiều View thành một tổ hợp duy nhất bằng cú pháp khai báo. Nó tự động bọc nhiều biểu thức, cấu trúc điều kiện và giá trị tùy chọn vào các kiểu tương ứng của chúng: TupleView, ConditionalContent, OptionalContent.
Trước khi result builder ra đời, các nhà phát triển phải tự tay bọc các phần tử trong VStack hoặc HStack, và sử dụng toán tử ba ngôi hoặc phương thức factory cho logic điều kiện. @ViewBuilder đã làm cho cú pháp SwiftUI trở nên ngắn gọn và dễ đọc, cho phép viết mã trông giống như Swift thông thường với if/else và vòng lặp.
Theo Swift Evolution SE-0289, result builders là một cơ chế chung không gắn liền với SwiftUI. @ViewBuilder là một triển khai của cơ chế này, cùng với @StringBuilder để xây dựng chuỗi và các triển khai thư viện cho các DSL khác. Trong SwiftUI, @ViewBuilder không chỉ được sử dụng cho body mà còn cho các tham số closure của container (VStack, HStack, ZStack, List).
Trong UIKit mệnh lệnh, bạn tạo rõ ràng một UIView, cấu hình các thuộc tính của nó và thêm nó vào hệ phân cấp qua addSubview. Trong SwiftUI với @ViewBuilder, bạn mô tả một cách khai báo những View nào sẽ được hiển thị, và SwiftUI quản lý việc tạo, cập nhật và xóa các phần tử dựa trên các thay đổi trạng thái.
Result builder là một cơ chế Swift biến đổi một chuỗi các biểu thức thành một giá trị tổng hợp duy nhất thông qua các phương thức tĩnh buildBlock, buildOptional, buildEither và các phương thức khác. Khi trình biên dịch nhìn thấy chú thích @ViewBuilder, nó tự động áp dụng các phương thức này vào khối mã trong quá trình biên dịch.
@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 chấp nhận từ 1 đến 10 biểu thức và trả về một TupleView. Mỗi số lượng (số lượng biểu thức) có overload buildBlock riêng: từ buildBlock
buildEither (first/second) xử lý các cấu trúc if/else. Mỗi nhánh được truyền vào phương thức tương ứng, và kết quả được bọc trong ConditionalContent — một kiểu ẩn các kiểu nhánh cụ thể và cung cấp một giao diện thống nhất cho SwiftUI.
Trong SwiftUI, thuộc tính body đã được chú thích ngầm định với @ViewBuilder — bạn không thấy chú thích này trong mã, nhưng trình biên dịch tự động áp dụng nó. Tuy nhiên, đối với các thuộc tính tùy chỉnh trả về nhiều View, hoặc cho các tham số closure, chú thích phải được chỉ định rõ ràng.
Giới hạn 1 — 10 phần tử trong một khối. Đây là giới hạn nổi tiếng nhất của @ViewBuilder. Nếu bạn cần hiển thị hơn 10 phần tử ở cùng một cấp, trình biên dịch sẽ báo lỗi. Các giải pháp bao gồm Group, ForEach, List hoặc chia thành các thành phần phụ. Group không thêm sự lồng nhau trực quan, nhưng mỗi Group được tính là một phần tử.
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")
}
}
}
Giới hạn 2 — thiếu hỗ trợ cho một số cấu trúc. @ViewBuilder không hỗ trợ do/catch, guard, for-in (không có ForEach) và các cấu trúc điều khiển luồng khác. Đối với vòng lặp, hãy sử dụng ForEach với dữ liệu có thể xác định. Để xử lý lỗi, hãy sử dụng các View riêng biệt chấp nhận Result hoặc giá trị tùy chọn.
Giới hạn 3 — độ phức tạp khi gỡ lỗi. Khi xảy ra lỗi trong @ViewBuilder, trình biên dịch tạo ra các thông báo dài dòng khó tìm ra nguyên nhân gốc. Các vấn đề điển hình: không khớp kiểu trong nhánh if/else, vượt quá giới hạn 10 phần tử hoặc thiếu các overload buildBlock cần thiết.
Mẫu 1: hiển thị có điều kiện qua if/else. Trường hợp sử dụng phổ biến nhất của @ViewBuilder. Cho phép hiển thị các View khác nhau dựa trên trạng thái mà không cần sử dụng toán tử ba ngôi hoặc phương thức 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)
}
}
}
Mẫu 2: @ViewBuilder trong tham số hàm và bộ khởi tạo. Được sử dụng để tạo các container tái sử dụng chấp nhận các View con thông qua closure. Đây là mẫu tiêu chuẩn cho các thư viện và thành phần 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)
}
}
Mẫu 3: kết hợp với ForEach. @ViewBuilder hoạt động chính xác với ForEach, cho phép tạo động các phần tử từ một mảng dữ liệu. Mỗi phần tử của ForEach được tính là một biểu thức trong ngữ cảnh của @ViewBuilder.
ViewBuilder tùy chỉnh là một hàm hoặc thuộc tính do người dùng định nghĩa được chú thích với @ViewBuilder và trả về some View. Các hàm như vậy cho phép đóng gói logic hiển thị phức tạp và tái sử dụng nó trong các phần khác nhau của ứng dụng.
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
}
}
}
// Sử dụng:
FormRow(label: "Name") {
TextField("Enter name", text: $name)
}
FormRow(label: "Gender") {
Picker("Select", selection: $gender) {
Text("Nam").tag(Gender.male)
Text("Nữ").tag(Gender.female)
}
}
Quy tắc quan trọng: một hàm tùy chỉnh với @ViewBuilder phải trả về some View, không phải một kiểu cụ thể hay giao thức View. Chỉ kiểu không minh bạch mới cho phép ẩn triển khai cụ thể trong khi vẫn giữ được tính linh hoạt của việc kết hợp.
Hiệu suất: các hàm @ViewBuilder tùy chỉnh không thêm chi phí so với mã trực tiếp trong body. Trình biên dịch inline các lời gọi và tối ưu hóa mã kết quả. Chia body thành các hàm @ViewBuilder cải thiện khả năng đọc mà không làm giảm hiệu suất.
Câu hỏi thường gặp
@ViewBuilder là một chú thích result builder biến đổi một khối mã với nhiều biểu thức và điều kiện thành một kiểu View duy nhất. Nó cho phép sử dụng cú pháp Swift quen thuộc (if/else, switch, biểu thức tùy chọn) bên trong UI khai báo của SwiftUI.
Giới hạn bắt nguồn từ triển khai của buildBlock — có một overload riêng của phương thức cho mỗi số lượng từ 1 đến 10. Swift không hỗ trợ generics variadic, vì vậy số lượng overload là cố định. Để khắc phục, hãy sử dụng Group, ForEach hoặc các thành phần phụ.
Không, giao thức View áp dụng ngầm định @ViewBuilder cho thuộc tính body. Tuy nhiên, đối với các thuộc tính tùy chỉnh, phương thức và tham số closure trả về nhiều View, chú thích phải được chỉ định rõ ràng. Nếu không có nó, trình biên dịch sẽ không thể xử lý nhiều biểu thức.
Đối với biểu thức tùy chọn, phương thức buildIf được sử dụng, phương thức này chấp nhận một View tùy chọn và trả về nó nếu có giá trị. Nếu giá trị là nil, buildIf trả về nil và phần tử không được hiển thị. Điều này cho phép sử dụng if let bên trong body.
Có, kể từ Swift 5.9 @ViewBuilder hỗ trợ switch thông qua phương thức buildExpression. Trình biên dịch biến đổi mỗi nhánh case thành lời gọi buildEither tương ứng. Hỗ trợ switch làm cho mã dễ đọc hơn so với các cấu trúc if/else lồng nhau.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm