Я пытался создать многоразовый API-интерфейс SwiftUI View, который принимает необязательный аргумент RandomAccessCollection и выводит соответствующий результат, как это делает List. Например...
MyContainer(things) { thing in
Text("\(thing.name)")
}
MyContainer {
Text("\(thingA.name)")
Text("\(thingB.name)")
}
Я понимаю дочерние параметры @ViewBuilder
и думаю, что у меня есть инициализация с помощью параметрического полиморфизма (where Data: RandomAccessCollection, ...
и where Data == Never, ...
), за исключением того, что я рвал на себе волосы, пытаясь вернуть другой var body: some View
результат в зависимости от того, были ли предоставлены данные.
Для этого вы можете использовать @ViewBuilder
и ForEach(subviewOf:content:), например
struct MyContainer<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
ForEach(subviewOf: content) { subview in
subview
}
}
}
См. Демистификация контейнеров SwiftUI WWDC 2024 в 3:30 для получения дополнительной информации.
О, ведущий в видео упомянул именно эту особенность List, поэтому я предположил, что их пример кода будет делать то же самое, странно, что они ее не реализовали.
Не следует «возвращать разные body
в зависимости от того, предоставлены ли данные». Вам следует «объединить» два случая в инициализаторах.
Посмотрите, как реализован List
— List
имеет 2 параметра типа, SelectionValue
(которые я рассматривать не буду) и Content
. Когда вы используете инициализатор без данных, Content
устанавливается на любое предоставленное вами представление. Когда вы используете некоторые данные для заполнения списка, Content
устанавливается на ForEach<Data, Data.Element.ID, RowContent>
.
Вот подписи двух инициализаторов:
init(@ViewBuilder content: () -> Content)
init<Data, RowContent>(
_ data: Data,
@ViewBuilder rowContent: @escaping (Data.Element) -> RowContent
) where Content == ForEach<Data, Data.Element.ID, RowContent>, Data : RandomAccessCollection, RowContent : View, Data.Element : Identifiable
Обратите внимание на ограничение Content == ForEach<...>
для второго инициализатора.
Итак, вам следует сделать то же самое:
struct CustomList<Content: View>: View {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
init<Data, RowContent>(
_ data: Data,
@ViewBuilder rowContent: @escaping (Data.Element) -> RowContent
) where Content == ForEach<Data, Data.Element.ID, RowContent>, Data : RandomAccessCollection, RowContent : View, Data.Element : Identifiable {
content = ForEach(data, content: rowContent)
}
var body: some View { ... }
}
Теперь body
тривиально — просто используйте content
, независимо от того, какой инициализатор вызвал пользователь. Если вы хотите извлечь каждое подпредставление, переданное пользователем, используйте ForEach(subviewOf:)
, как советует Малхал.
Если вы используете более раннюю версию iOS, вы все равно можете использовать View Extractor. Например, вы можете расположить подпредставления вертикально и добавить к каждому подпредставлению желтый фон и некоторые отступы:
var body: some View {
VStack {
//ForEach(subviewOf: content) { view in
ExtractMulti(content) { view in
view
.background(.yellow)
.padding()
}
}
}
Отличный! Обертывание данных и дочернего содержимого во время инициализации — это то, чего мне не хватало. Я тщетно пытался сделать это в теле. Спасибо!
Спасибо за комментарий, но, как упоминалось выше, у меня не было проблем с контентом для детского просмотра. Моя проблема заключалась в разрешении двух исходов; один с данными и один без. Извините, я не выразился более ясно.