Похоже, что новая SwiftUI
структура Apple использует новый синтаксис, который эффективно строит кортеж, но имеет другой синтаксис:
var body: some View {
VStack(alignment: .leading) {
Text("Hello, World") // No comma, no separator ?!
Text("Hello World!")
}
}
Пытаясь разобраться в том, чем на самом деле является этот синтаксис , я обнаружил, что VStack
используемый здесь инициализатор принимает закрытие типа () -> Content
в качестве второго параметра, где Content
общий параметр, соответствующий тому, View
который выводится через закрытие. Чтобы узнать, какой тип Content
подразумевается, я немного изменил код, сохранив его функциональность:
var body: some View {
let test = VStack(alignment: .leading) {
Text("Hello, World")
Text("Hello World!")
}
return test
}
При этом test
оказывается VStack<TupleView<(Text, Text)>>
, что он типичен, то Content
есть типичен TupleView<Text, Text>
. Посмотрев вверх TupleView
, я обнаружил, что это тип-оболочка, происходящий от SwiftUI
самого себя, который может быть инициализирован только путем передачи кортежа, который он должен обернуть.
Вопрос
Теперь мне интересно, как два Text
экземпляра в этом примере конвертируются в TupleView<(Text, Text)>
. Это взломано SwiftUI
и, следовательно, недопустимо для обычного синтаксиса Swift? TupleView
принадлежность к SwiftUI
типу подтверждает это предположение. Или это действительный синтаксис Swift? Если да, то как его использовать на улице SwiftUI
?
@ViewBuilder
developer.apple.com/documentation/swiftui/viewbuilder .Ответы:
Как говорит Мартин , если вы посмотрите документацию для
VStack
'sinit(alignment:spacing:content:)
, вы увидите, чтоcontent:
параметр имеет атрибут@ViewBuilder
:init(alignment: HorizontalAlignment = .center, spacing: Length? = nil, @ViewBuilder content: () -> Content)
Этот атрибут относится к
ViewBuilder
типу, который, если вы посмотрите на сгенерированный интерфейс, выглядит так:@_functionBuilder public struct ViewBuilder { /// Builds an empty view from an block containing no statements, `{ }`. public static func buildBlock() -> EmptyView /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) /// through unmodified. public static func buildBlock(_ content: Content) -> Content where Content : View }
@_functionBuilder
Атрибут является частью неофициальной функции , называемой « функции строителей », которая была станом на эволюцию Swift здесь и реализованного специально для версии Swift , который поставляется с Xcode 11, что позволяет использовать его в SwiftUI.Маркировка типа
@_functionBuilder
позволяет использовать его в качестве настраиваемого атрибута в различных объявлениях, таких как функции, вычисляемые свойства и, в данном случае, параметры типа функции. Такие аннотированные объявления используют построитель функций для преобразования блоков кода:Способ, которым построитель функций преобразует код, определяется его реализацией методов построителя, таких как
buildBlock
, который принимает набор выражений и объединяет их в одно значение.Например,
ViewBuilder
реализуетbuildBlock
от 1 до 10View
соответствующих параметров, объединяя несколько представлений в одноTupleView
:@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension ViewBuilder { /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) /// through unmodified. 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 { // error: Static member 'leading' cannot be used on instance of // type 'HorizontalAlignment' 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") // ... // up to 10 views } Group { Text("Hello world") // ... // up to 10 more views } // ... }
ViewBuilder
также реализует другие методы построения функций, такие как:extension ViewBuilder { /// Provides support for "if" statements in multi-statement closures, producing /// ConditionalContent for the "then" branch. public static func buildEither<TrueContent, FalseContent>(first: TrueContent) -> ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View /// Provides support for "if-else" statements in multi-statement closures, /// producing ConditionalContent for the "else" branch. 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") ) } }
(выдача избыточных вызовов с
ViewBuilder.buildBlock
одним аргументом для ясности).источник
ViewBuilder
определяет толькоbuildBlock
до 10 параметров - означает ли это, чтоvar body: some View
не может быть больше 11 подвидов?ForEach
вместо этого что-то вроде представления. Однако вы можете использоватьGroup
представление, чтобы обойти это ограничение, я отредактировал свой ответ, чтобы показать это.Аналогичная вещь описана в видео Что нового в Swift WWDC в разделе о DSL (начало ~ 31: 15). Атрибут интерпретируется компилятором и переводится в связанный код:
источник