Массив, включающий несколько типов представлений в Swift / SwiftUI

Aug 18 2020

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

Структура:

struct BackgroundImage : Identifiable{
var background : AnyView
let id = UUID()
}

Я добавляю их в такой массив

ViewModel:

class VM : ObservableObject{
    @Published var imageViews : Array<BackgroundImage> = Array<BackgroundImage>()
        
    init(){
        imageViews.append(BackgroundImage(background: AnyView(Rectangle().foregroundColor(Color.green))))
        imageViews.append(BackgroundImage(background: AnyView(Rectangle().foregroundColor(Color.yellow))))
        imageViews.append(BackgroundImage(background: AnyView(Image("Testimage"))))
    }

что позволяет мне перебирать массив BackgroundImages вроде этого

Посмотреть:

LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())]) {
    ForEach(VM.imageViews, id: \.self.id) { view in
        ZStack{
            view.background
            //.resizable()
            //.aspectRatio(contentMode: .fill)
            .frame(width: g.size.width/2.25, height: g.size.height/8)                                                                                  
        .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
        }
    }
}

Однако я не могу добавить

 .resizable()
 .aspectRatio(contentMode: .fill)

для изображений, поскольку AnyView этого не допускает.

Есть ли лучший способ добиться этого? Должен ли я вместо этого иметь два отдельных массива для фигур / изображений? Или есть альтернативная структура View, которая больше подходит для этого?

Благодаря!

Ответы

1 vacawama Aug 18 2020 at 19:51

Как упоминал @ DávidPásztor в комментариях, хранить представления в вашей ViewModel - плохой дизайн.

На самом деле вам просто нужно сохранить цвет и имя изображения. Позвольте коду построения представления конструировать фактические представления.

Вот возможная реализация.

struct BackgroundItem: Identifiable {
    private let uicolor: UIColor?
    private let imageName: String?
    let id = UUID()
    
    var isImage: Bool { imageName != nil }
    var color: UIColor { uicolor ?? .white }
    var name: String { imageName ?? "" }
    
    init(name: String? = nil, color: UIColor? = nil) {
        imageName = name
        uicolor = color
    }
}

class VM : ObservableObject{
    @Published var imageItems: [BackgroundItem] = []
        
    init() {
        imageItems = [.init(color: .green),
                      .init(color: .blue),
                      .init(name: "TestImage")]
    }
}

struct ContentView: View {
    @ObservedObject var vm = VM()

    var body: some View {
        GeometryReader { g in
            LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())]) {
                ForEach(vm.imageItems) { item in
                    ZStack{
                        if item.isImage {
                            Image(item.name)
                                .resizable()
                                .aspectRatio(contentMode: .fill)
                        } else {
                            Color(item.color)
                        }
                    }
                    .frame(width: g.size.width/2.25, height: g.size.height/8)
                    .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)
                }
            }
        }
    }
}

Примечание: я подумал об использовании, enumчтобы сохранить различие между imageName и цветом, но было проще всего использовать дополнительные параметры и сохранить тот, который мне нужен. С интерфейсом, который предоставляется коду построения представления, вы можете легко изменить реализацию на то, как вы хотите хранить информацию.