Я могу сделать статический список, например
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()
}
}
}
Есть ли какая-нибудь работа для этого? Я пытаюсь выполнить список, содержащий динамический набор элементов, которые не вводятся статически.





Оператор управления потоком 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()
}
}
}
}
}
Он будет строиться, но не будет работать. использование is всегда заставит поток управления перейти к части else оператора If. is не может работать, потому что типы находятся внутри массива (не экземпляры).
Можно ли вернуть разные View в зависимости от потребностей?
Короче: Вроде, как бы, что-то вроде
Как это полностью описано в Swift.org, НЕВОЗМОЖНО иметь несколько Тип, возвращающихся как непрозрачный тип.
If a function with an opaque return type returns from multiple places, all of the possible return values must have the same type. For a generic function, that return type can use the function’s generic type parameters, but it must still be a single type.
Итак, как List может сделать это, когда статически передается несколько разных представлений?
Список не возвращает разные типы, он возвращает EmptyView, заполненный некоторым представлением контента. Конструктор может создавать оболочку вокруг любого типа представления, которое вы ему передаете, но когда вы используете все больше и больше представлений, он вообще не будет компилироваться! (попробуйте например пройти более 10 просмотров и посмотрите что получится)
Как видите, List содержимое — это своего рода ListCoreCellHost, содержащее подмножество представлений, которое доказывает, что это просто контейнер того, что оно представляет.
Что делать, если у меня много данных (например, контактов) и я хочу заполнить для этого список?
Вы можете соответствовать Identifiable или использовать функцию identified(by:), как описано здесь.
Что, если у любого контакта может быть другое мнение?
Раз вы называете их контактными, значит, это одно и то же! Вы должны рассмотреть ООП, чтобы сделать их одинаковыми и использовать inheritance преимущества. Но в отличие от UIKit, SwiftUI основан на структурах. Они не могут наследовать друг друга.
Итак, каково решение?
Вы ДОЛЖЕН объединяете все виды представлений, которые хотите отображать, в один тип View. Документации для EmptyView недостаточно, чтобы воспользоваться этим (пока). НО!!!, к счастью, вы можете использовать UIKit
Как я могу воспользоваться UIKit для этого
View1 и View2 поверх UIKit.ContainerView с помощью UIKit.ContainerView способ, который принимает аргумент и представляет View1 или View2 и размер, чтобы соответствовать.UIViewRepresentable и выполнять его требования.List, чтобы показать список ContainerView
Итак, теперь это один тип, который может представлять несколько представлений.Я думаю, что здесь есть недоразумение. Созданный вами список будет содержать только книги. Я искал способ создать Книги, Машины, X, Y, Z. Список должен содержать что угодно.
Вы можете использовать динамический список подпредставлений, но вам нужно быть осторожным с типами и экземплярами. Для справки, это демонстрация динамического «гамбургера», 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)
}
}
Этот синтаксис List больше не работает, кажется, теперь вам нужно иметь List {ForEach(["1", "2", "3", "4", "5"], id: \.self) { row in
Похоже, ответ был связан с переносом моего представления внутрь 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 View2.Type: return AnyView( View2() )
default: return AnyView(EmptyView())
}
}
}
Теперь я могу получать данные просмотра с сервера и составлять их. Кроме того, они создаются только тогда, когда это необходимо.
Я нашел немного более простой способ, чем ответы выше.
Создайте свой собственный вид.
Убедитесь, что ваш просмотр Идентифицируемый
(Это говорит SwiftUI, что он может различать представления внутри ForEach, просматривая их свойство id)
Например, скажем, вы просто добавляете изображения в HStack, вы можете создать собственное представление SwiftUI, например:
struct MyImageView: View, Identifiable {
// Conform to Identifiable:
var id = UUID()
// Name of the image:
var imageName: String
var body: some View {
Image(imageName)
.resizable()
.frame(width: 50, height: 50)
}
}
Затем в вашем HStack:
// Images:
HStack(spacing: 10) {
ForEach(images, id: \.self) { imageName in
MyImageView(imageName: imageName)
}
Spacer()
}
Вы можете сделать это с помощью полиморфизма:
struct View1: View {
var body: some View {
Text("View1")
}
}
struct View2: View {
var body: some View {
Text("View2")
}
}
class ViewBase: Identifiable {
func showView() -> AnyView {
AnyView(EmptyView())
}
}
class AnyView1: ViewBase {
override func showView() -> AnyView {
AnyView(View1())
}
}
class AnyView2: ViewBase {
override func showView() -> AnyView {
AnyView(View2())
}
}
struct ContentView: View {
let views: [ViewBase] = [
AnyView1(),
AnyView2()
]
var body: some View {
List(self.views) { view in
view.showView()
}
}
}
Теперь вы можете использовать операторы управления потоком непосредственно в блоках @ViewBuilder, что означает, что следующий код абсолютно корректен:
struct ContentView: View {
let elements: [Any] = [View1.self, View2.self]
var body: some View {
List {
ForEach(0 ..< elements.count) { index in
if let _ = elements[index] as? View1 {
View1()
} else {
View2()
}
}
}
}
}
В дополнение к принятому отвечать вы можете использовать @ViewBuilder и полностью избегать AnyView:
@ViewBuilder
func buildView(types: [Any], index: Int) -> some View {
switch types[index].self {
case is View1.Type: View1()
case is View2.Type: View2()
default: EmptyView()
}
}
Свифт 5
это, кажется, работает для меня.
struct AMZ1: View {
var body: some View {
Text("Text")
}
}
struct PageView: View {
let elements: [Any] = [AMZ1(), AMZ2(), AMZ3()]
var body: some View {
TabView {
ForEach(0..<elements.count) { index in
if self.elements[index] is AMZ1 {
AMZ1()
} else if self.elements[index] is AMZ2 {
AMZ2()
} else {
AMZ3()
}
}
}
import SwiftUI
struct ContentView: View {
var animationList: [Any] = [
AnimationDemo.self, WithAnimationDemo.self, TransitionDemo.self
]
var body: some View {
NavigationView {
List {
ForEach(0..<animationList.count) { index in
NavigationLink(
destination: animationIndex(types: animationList, index: index),
label: {
listTitle(index: index)
})
}
}
.navigationBarTitle("Animations")
}
}
@ViewBuilder
func listTitle(index: Int) -> some View {
switch index {
case 0:
Text("AnimationDemo").font(.title2).bold()
case 1:
Text("WithAnimationDemo").font(.title2).bold()
case 2:
Text("TransitionDemo").font(.title2).bold()
default:
EmptyView()
}
}
@ViewBuilder
func animationIndex(types: [Any], index: Int) -> some View {
switch types[index].self {
case is AnimationDemo.Type:
AnimationDemo()
case is WithAnimationDemo.Type:
WithAnimationDemo()
case is TransitionDemo.Type:
TransitionDemo()
default:
EmptyView()
}
}
}
введите описание изображения здесь
Пожалуйста, добавьте некоторое объяснение к вашему ответу, чтобы другие могли извлечь из него уроки.
Как сейчас написано, ваш ответ неясен. Пожалуйста, редактировать, чтобы добавить дополнительную информацию, которая поможет другим понять, как это относится к заданному вопросу. Дополнительную информацию о том, как писать хорошие ответы, можно найти в справочном центре.
Почему бы не просто
self.elements[index] is View1?