Многоразовый API представления SwiftUI с данными и без них, например List

Я пытался создать многоразовый 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 результат в зависимости от того, были ли предоставлены данные.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
74
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Для этого вы можете использовать @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 для получения дополнительной информации.

Спасибо за комментарий, но, как упоминалось выше, у меня не было проблем с контентом для детского просмотра. Моя проблема заключалась в разрешении двух исходов; один с данными и один без. Извините, я не выразился более ясно.

Kromatic 28.06.2024 20:25

О, ведущий в видео упомянул именно эту особенность List, поэтому я предположил, что их пример кода будет делать то же самое, странно, что они ее не реализовали.

malhal 29.06.2024 10:32
Ответ принят как подходящий

Не следует «возвращать разные body в зависимости от того, предоставлены ли данные». Вам следует «объединить» два случая в инициализаторах.

Посмотрите, как реализован ListList имеет 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()
        }
    }
}

Отличный! Обертывание данных и дочернего содержимого во время инициализации — это то, чего мне не хватало. Я тщетно пытался сделать это в теле. Спасибо!

Kromatic 28.06.2024 20:22

Другие вопросы по теме