Хранить массив представлений SwiftUI

#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 просматривать в одном и том же закрытии.