Как иметь динамический список представлений с помощью SwiftUI

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

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()
          }
    }
}

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

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

Ответы 9

Оператор управления потоком 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()
                }
            }
        }
    }
}

Почему бы не просто self.elements[index] is View1?

user28434'mstep 18.06.2019 11:31

Он будет строиться, но не будет работать. использование is всегда заставит поток управления перейти к части else оператора If. is не может работать, потому что типы находятся внутри массива (не экземпляры).

Just a coder 18.06.2019 11:48

Можно ли вернуть разные 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 и выполнять его требования.
  • Сделайте свой SwiftUIList, чтобы показать список ContainerView Итак, теперь это один тип, который может представлять несколько представлений.

Я думаю, что здесь есть недоразумение. Созданный вами список будет содержать только книги. Я искал способ создать Книги, Машины, X, Y, Z. Список должен содержать что угодно.

Just a coder 18.06.2019 17:23

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

malhal 08.01.2020 14:40
Ответ принят как подходящий

Похоже, ответ был связан с переносом моего представления внутрь 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()
        }
    }
}

SwiftUI 2

Теперь вы можете использовать операторы управления потоком непосредственно в блоках @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()
                }
            }
        }
    }
}

SwiftUI 1

В дополнение к принятому отвечать вы можете использовать @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()
        }
    }
}

введите описание изображения здесь

Пожалуйста, добавьте некоторое объяснение к вашему ответу, чтобы другие могли извлечь из него уроки.

Nico Haase 13.10.2021 17:56

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

Community 13.10.2021 22:36

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