Như Martin nói , nếu bạn xem tài liệu về VStack
's init(alignment:spacing:content:)
, bạn có thể thấy rằng content:
tham số có thuộc tính @ViewBuilder
:
init(alignment: HorizontalAlignment = .center, spacing: Length? = nil,
@ViewBuilder content: () -> Content)
Thuộc tính này đề cập đến ViewBuilder
loại, nếu bạn nhìn vào giao diện đã tạo, sẽ giống như:
@_functionBuilder public struct ViewBuilder {
public static func buildBlock() -> EmptyView
public static func buildBlock(_ content: Content) -> Content
where Content : View
}
Các @_functionBuilder
thuộc tính là một phần của một tính năng không chính thức gọi là " xây dựng chức năng ", đã được dốc trên Swift tiến hóa ở đây , và thực hiện đặc biệt dành cho các phiên bản của Swift mà tàu với Xcode 11, cho phép nó được sử dụng trong SwiftUI.
Việc đánh dấu một kiểu @_functionBuilder
cho phép nó được sử dụng như một thuộc tính tùy chỉnh trên các khai báo khác nhau, chẳng hạn như hàm, thuộc tính được tính toán và trong trường hợp này là các tham số của kiểu hàm. Các khai báo được chú thích như vậy sử dụng trình tạo hàm để chuyển đổi các khối mã:
- Đối với các hàm có chú thích, khối mã được chuyển đổi là phần thực hiện.
- Đối với các thuộc tính tính toán có chú thích, khối mã được chuyển đổi là getter.
- Đối với các tham số được chú thích của kiểu hàm, khối mã được chuyển đổi là bất kỳ biểu thức đóng nào được chuyển cho nó (nếu có).
Cách thức mà trình tạo hàm chuyển đổi mã được xác định bằng cách triển khai các phương thức của trình tạo, chẳng hạn như buildBlock
, nhận một tập hợp các biểu thức và hợp nhất chúng thành một giá trị duy nhất.
Ví dụ: ViewBuilder
triển khai buildBlock
từ 1 đến 10 View
tham số phù hợp, hợp nhất nhiều chế độ xem thành một 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
}
Điều này cho phép một tập hợp các biểu thức khung nhìn trong một bao đóng được chuyển tới trình VStack
khởi tạo của 's được chuyển thành một lệnh gọi buildBlock
có cùng số đối số. Ví dụ:
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
Text("Hello, World")
Text("Hello World!")
}
}
}
được chuyển đổi thành một cuộc gọi đến buildBlock(_:_:)
:
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
ViewBuilder.buildBlock(Text("Hello, World"), Text("Hello World!"))
}
}
}
dẫn đến loại kết quả không rõ ràng some View
được thỏa mãn bởi TupleView<(Text, Text)>
.
Bạn sẽ lưu ý rằng ViewBuilder
chỉ xác định buildBlock
tối đa 10 tham số, vì vậy nếu chúng tôi cố gắng xác định 11 lượt xem phụ:
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!")
}
}
chúng tôi gặp lỗi trình biên dịch, vì không có phương pháp trình tạo nào để xử lý khối mã này (lưu ý rằng vì tính năng này vẫn đang trong quá trình làm việc, các thông báo lỗi xung quanh nó sẽ không hữu ích).
Trong thực tế, tôi không tin rằng mọi người sẽ thường xuyên gặp phải hạn chế này, ví dụ: ví dụ trên sẽ được phục vụ tốt hơn bằng cách sử dụng ForEach
chế độ xem thay thế:
var body: some View {
VStack(alignment: .leading) {
ForEach(0 ..< 20) { i in
Text("Hello world \(i)")
}
}
}
Tuy nhiên, nếu bạn cần nhiều hơn 10 dạng xem được xác định tĩnh, bạn có thể dễ dàng giải quyết hạn chế này bằng cách sử dụng dạng Group
xem:
var body: some View {
VStack(alignment: .leading) {
Group {
Text("Hello world")
}
Group {
Text("Hello world")
}
}
ViewBuilder
cũng triển khai các phương thức xây dựng hàm khác như:
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
}
Điều này cung cấp cho nó khả năng xử lý các câu lệnh if:
var body: some View {
VStack(alignment: .leading) {
if .random() {
Text("Hello World!")
} else {
Text("Goodbye World!")
}
Text("Something else")
}
}
được chuyển thành:
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")
)
}
}
(phát ra lệnh gọi 1 đối số dư thừa để ViewBuilder.buildBlock
làm rõ).
@ViewBuilder
developer.apple.com/documentation/swiftui/viewbuilder .