#swift #swiftui
Вопрос:
Я хотел бы сохранить массив представлений SwiftUI, переданных в инициализаторе в стиле SwiftUI. Эти представления не обязательно все будут одного типа.
На данный момент у меня есть это, которое работает для представлений одного и того же типа:
struct ViewHolder<Content: View> {
var content: () -> Content
let id = UUID()
init(@ViewBuilder _ content: @escaping () -> Content) {
self.content = content
}
}
struct ViewDisplayer<Content: View>: View {
var views: [ViewHolder<Content>]
init(@ArrayBuilder<ViewHolder<Content>> content: () -> [ViewHolder<Content>]) {
self.views = content()
}
var body: some View {
ForEach(views, id: .id) {view in
view.content()
}
}
}
@resultBuilder
public class ArrayBuilder<Element> {
public static func buildBlock(_ elements: Element...) -> [Element] {
return elements
}
}
Это можно использовать следующим образом:
struct ContentView: View {
var body: some View {
ViewDisplayer {
ViewHolder {
Text("Hi")
}
ViewHolder {
Text("Hi")
}
}
}
}
Однако он ломается, если не все владельцы просмотров имеют одинаковое содержимое:
ViewHolder {
Text("Hi")
}
ViewHolder { //Cannot convert value of type 'ViewHolder<Button<Text>>' to expected argument type 'ViewHolder<Text>'
Button("") {
}
}
Я бы предпочел не заворачивать все в AnyView(...)
какие-нибудь идеи?
Комментарии:
1. Если вы хотите сохранить массив неоднородных
View
s,AnyView
это ваш единственный выбор. Тем не менее, я бы все равно изучил вариант другого архитектурного подхода, поскольку я считаю (не могу доказать документально), чтоView
подобное хранение является анти-шаблоном в SwiftUI. Я бы, например, выбрал хранение модели, представляющей различные типы представлений (enum
возможно, со связанными значениями), и на основе этого визуализировал иерархию представлений.2. Каков возможный вариант использования, необходимый для этого?
3. На самом деле это не имеет ничего общего со SwiftUI — это быстрая статическая типизация,
ViewHolder<Text> != ViewHodler<Button>
такая же, какInt != String
. Ты же не пытаешься что-то добавитьString
Array<Int>
, верно? Почему вы пытаетесь это сделать с вышеуказанными типами? Просто переосмыслите дизайн, чтобы избежать использования массива для таких случаев (или использованияAnyView
в качестве одного типа).4. Будет ли Группа делать то, что вы хотите?
Ответ №1:
Я считаю, что вы слишком усложнили свой код.
Прежде всего, ваш пример определенно неполон, так как в вашем примере ваш самый верхний вид-это ForEach, что маловероятно. (Замените VStack
в моем примере любой самый верхний вид контейнера, который у вас есть.)
Но кроме этого, вам действительно не следует беспокоиться о точном типе ваших представлений, и просто создайте один ViewBuilder
такой:
import SwiftUI
import CoreData
struct VHolder<T: View>: View {
var content: T
init(@ViewBuilder content: @escaping () -> T) {
self.content = content()
}
var body: some View {
VStack {
content
}
}
}
struct VHolder_Previews: PreviewProvider {
static var previews: some View {
VHolder {
Text("Hi")
Button("Hi", action: { })
}
}
}
Если вы хотите встроить свои держатели представлений в «ViewDisplayer», это просто еще один слой, который должен быть таким же (если только у него нет другого представления контейнера — скажем HStack
):
struct HHolder<T: View>: View {
var content: T
init(@ViewBuilder content: @escaping () -> T) {
self.content = content()
}
var body: some View {
HStack {
content
}
}
}
struct HHolder_Previews: PreviewProvider {
static var previews: some View {
HHolder {
VHolder {
Text("Hi")
Button("Hi", action: { })
}
VHolder {
Text("Not Hi")
Button("Not Hi", action: { })
}
}
}
}
Этот код протестирован и работает на XCode 12.5. Вы можете видеть, что я мог бы добавлять Button
и Text
просматривать в одном и том же закрытии.