Parece que a nova SwiftUI
estrutura da Apple usa um novo tipo de sintaxe que efetivamente constrói uma tupla, mas tem outra sintaxe:
var body: some View {
VStack(alignment: .leading) {
Text("Hello, World") // No comma, no separator ?!
Text("Hello World!")
}
}
Tentando descobrir o que essa sintaxe realmente é , descobri que o VStack
inicializador usado aqui leva um encerramento do tipo () -> Content
como o segundo parâmetro, onde Content
é um parâmetro genérico em conformidade com o View
que é inferido por meio do encerramento. Para descobrir a que tipo Content
é inferido, mudei ligeiramente o código, mantendo sua funcionalidade:
var body: some View {
let test = VStack(alignment: .leading) {
Text("Hello, World")
Text("Hello World!")
}
return test
}
Com isso, test
revela-se do tipo VStack<TupleView<(Text, Text)>>
, ou seja, que Content
é do tipo TupleView<Text, Text>
. Olhando para cima TupleView
, descobri que é um tipo de wrapper originado de SwiftUI
si mesmo que só pode ser inicializado passando a tupla que deve envolver.
Questão
Agora estou me perguntando como no mundo as duas Text
instâncias neste exemplo são convertidas em um TupleView<(Text, Text)>
. Isso é hackeado SwiftUI
e, portanto, sintaxe Swift regular inválida? TupleView
ser um SwiftUI
tipo apóia essa suposição. Ou esta é uma sintaxe Swift válida? Se sim, como usá-lo fora SwiftUI
?
@ViewBuilder
developer.apple.com/documentation/swiftui/viewbuilder .Respostas:
Como Martin diz , se você olhar a documentação de
VStack
'sinit(alignment:spacing:content:)
, verá que ocontent:
parâmetro tem o atributo@ViewBuilder
:init(alignment: HorizontalAlignment = .center, spacing: Length? = nil, @ViewBuilder content: () -> Content)
Este atributo se refere ao
ViewBuilder
tipo, que se você olhar para a interface gerada, é semelhante a:@_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 }
O
@_functionBuilder
atributo é parte de um recurso não oficial chamado " construtores de função ", que foi lançado na evolução do Swift aqui , e implementado especialmente para a versão do Swift que acompanha o Xcode 11, permitindo que seja usado no SwiftUI.A marcação de um tipo
@_functionBuilder
permite que ele seja usado como um atributo personalizado em várias declarações, como funções, propriedades calculadas e, neste caso, parâmetros do tipo de função. Essas declarações anotadas usam o construtor de função para transformar blocos de código:A maneira pela qual um construtor de função transforma o código é definida por sua implementação de métodos de construtor , como
buildBlock
, que pega um conjunto de expressões e as consolida em um único valor.Por exemplo,
ViewBuilder
implementabuildBlock
de 1 a 10View
parâmetros em conformidade, consolidando várias visualizações em uma únicaTupleView
:@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 // ... }
Isso permite que um conjunto de expressões de exibição dentro de um encerramento passado ao
VStack
inicializador de seja transformado em uma chamada parabuildBlock
que leva o mesmo número de argumentos. Por exemplo:struct ContentView : View { var body: some View { VStack(alignment: .leading) { Text("Hello, World") Text("Hello World!") } } }
é transformado em uma chamada para
buildBlock(_:_:)
:struct ContentView : View { var body: some View { VStack(alignment: .leading) { ViewBuilder.buildBlock(Text("Hello, World"), Text("Hello World!")) } } }
resultando no tipo de resultado opaco
some View
sendo satisfeito porTupleView<(Text, Text)>
.Você notará que
ViewBuilder
define apenasbuildBlock
até 10 parâmetros, portanto, se tentarmos definir 11 subvisualizações: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!") } }
obtemos um erro do compilador, pois não há nenhum método do construtor para lidar com esse bloco de código (observe que, como esse recurso ainda é um trabalho em andamento, as mensagens de erro em torno dele não serão tão úteis).
Na realidade, não acredito que as pessoas vão se deparar com essa restrição com tanta frequência, por exemplo, o exemplo acima seria melhor servido usando a
ForEach
visualização:var body: some View { VStack(alignment: .leading) { ForEach(0 ..< 20) { i in Text("Hello world \(i)") } } }
No entanto, se você precisar de mais de 10 visualizações definidas estaticamente, pode facilmente contornar essa restrição usando a
Group
visualização: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
também implementa outros métodos construtores de função, como: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 }
Isso dá a ele a capacidade de lidar com declarações if:
var body: some View { VStack(alignment: .leading) { if .random() { Text("Hello World!") } else { Text("Goodbye World!") } Text("Something else") } }
que se transforma em:
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") ) } }
(emitindo chamadas redundantes de 1 argumento para
ViewBuilder.buildBlock
para maior clareza).fonte
ViewBuilder
define apenasbuildBlock
até 10 parâmetros - isso significa quevar body: some View
não pode ter mais de 11 subvisualizações?ForEach
visualização. No entanto, você pode usar aGroup
visualização para contornar essa restrição, editei minha resposta para mostrar isso.Uma coisa análoga é descrita em O que há de novo no vídeo Swift WWDC na seção sobre DSLs (começa em ~ 31:15). O atributo é interpretado pelo compilador e traduzido no código relacionado:
fonte