Как создать динамический список представлений с помощью SwiftUI - PullRequest
4 голосов
/ 18 июня 2019

Я могу сделать статический список как

List {
   View1()
   View2()
}

Но как мне сделать динамический список элементов из массива?Я попробовал следующее, но получил ошибку: Замыкание, содержащее оператор потока управления, нельзя использовать с конструктором функций 'ViewBuilder'

    let elements: [Any] = [View1.self, View2.self]

    List {
       ForEach(0..<elements.count) { index in
          if let _ = elements[index] as? View1 {
             View1()
          } else {
             View2()
          }
    }
}

Есть ли способ обойти это?Я пытаюсь выполнить динамический набор элементов списка, которые не вводятся статически.

Ответы [ 4 ]

2 голосов
/ 20 июня 2019

Похоже, что ответ был связан с переносом моего взгляда в AnyView

struct ContentView : View {
    var myTypes: [Any] = [View1.self, View2.self]
    var body: some View {
        List {
            ForEach(0..<myTypes.count) { index in
                self.buildView(types: self.myTypes, index: index)
            }
        }
    }

    func buildView(types: [Any], index: Int) -> AnyView {
        switch types[index].self {
           case is View1.Type: return AnyView( View1() )
           case is View1.Type: return AnyView( View2() )
           default: return AnyView(EmptyView())
        }
    }

Теперь я могу получать данные просмотра с сервера и составлять их. Кроме того, они создаются только при необходимости.

2 голосов
/ 19 июня 2019

Вы можете использовать динамический список подпредставлений, но вы должны быть осторожны с типами и экземплярами.Для справки, это демонстрационный пример динамического «гамбургера», github / swiftui_hamburger .

// Pages View to select current page
/// This could be refactored into the top level
struct Pages: View {
    @Binding var currentPage: Int
    var pageArray: [AnyView]

    var body: AnyView {
        return pageArray[currentPage]
    }
}

// Top Level View
/// Create two sub-views which, critially, need to be cast to AnyView() structs
/// Pages View then dynamically presents the subviews, based on currentPage state
struct ContentView: View {
    @State var currentPage: Int = 0

    let page0 = AnyView(
        NavigationView {
            VStack {
                Text("Page Menu").color(.black)

                List(["1", "2", "3", "4", "5"].identified(by: \.self)) { row in
                    Text(row)
                }.navigationBarTitle(Text("A Page"), displayMode: .large)
            }
        }
    )

    let page1 = AnyView(
        NavigationView {
            VStack {
                Text("Another Page Menu").color(.black)

                List(["A", "B", "C", "D", "E"].identified(by: \.self)) { row in
                    Text(row)
                }.navigationBarTitle(Text("A Second Page"), displayMode: .large)
            }
        }
    )

    var body: some View {
        let pageArray: [AnyView] = [page0, page1]

        return Pages(currentPage: self.$currentPage, pageArray: pageArray)

    }
}
0 голосов
/ 18 июня 2019

Можно ли вернуть разные View с в зависимости от потребностей?

Короче говоря: Сортировка

Как это полностью описано в swift.org , IMPOSSIIBLE иметь несколько Type s, возвращающихся как opaque type

Если функция с непрозрачным типом возврата возвращается из нескольких мест, все возможные возвращаемые значения должны иметь один и тот же тип. Для универсальной функции этот возвращаемый тип может использовать параметры универсального типа функции, но он все равно должен быть одного типа.

Так как же List может сделать это, когда статически передаются несколько разных представлений?

Список не возвращает разные типы, он возвращает EmptyView, заполненный некоторым представлением содержимого. Разработчик может создать оболочку вокруг любого типа представлений, которые вы ему передаете, но когда вы используете все больше и больше представлений, он даже не собирается компилироваться вообще! (попробуйте, например, пройти более 10 просмотров и посмотреть, что получится)

enter image description here

Как видите, содержимое List - это своего рода ListCoreCellHost, содержащее подмножество представлений, доказывающее, что это просто контейнер того, что оно представляет.

Что если у меня много данных (например, контактов) и я хочу заполнить список для этого?

Вы можете соответствовать Identifiable или использовать функцию identified(by:), как описано здесь .

Что, если какой-либо контакт может иметь другое представление?

Когда вы называете их контактными, это означает, что они одно и то же! Вы должны рассмотреть ООП, чтобы сделать их одинаковыми и использовать inheritance преимущества. Но в отличие от UIKit, SwiftUI основан на структурах. Они не могут наследовать друг друга.

Так в чем же решение?

Вы ДОЛЖНЫ обернуть все виды представлений, которые вы хотите отобразить, в один тип View. Документации для EmptyView недостаточно, чтобы воспользоваться этим (пока). НО !!! к счастью, вы можете использовать UIKit

Как я могу воспользоваться UIKit для этого

  • Реализация View1 и View2 поверх UIKit.
  • Определите ContainerView с UIKit.
  • Реализуйте ContainerView способ, который принимает аргумент и представляет View1 или View2 и размер для соответствия.
  • Соответствовать UIViewRepresentable и выполнять его требования.
  • Сделайте ваш SwiftUI List, чтобы показать список ContainerView Так что теперь это один тип, который может представлять несколько представлений
0 голосов
/ 18 июня 2019

if/let оператор управления потоком не может использоваться в блоке @ViewBuilder.

Операторы управления потоком внутри этих специальных блоков переводятся в структуры.

например,

if (someBool) {
    View1()
} else {
    View2()
}

переводится в ConditionalValue<View1, View2>.

Не все операторы управления потоком доступны внутри этих блоков, т. Е. switch, но это может измениться в будущем.

Подробнее об этомв предложении об эволюции компоновщика функций .


В вашем конкретном примере вы можете переписать код следующим образом:

struct ContentView : View {

    let elements: [Any] = [View1.self, View2.self]

    var body: some View {
        List {
            ForEach(0..<elements.count) { index in
                if self.elements[index] is View1 {
                    View1()
                } else {
                    View2()
                }
            }
        }
    }
}
...