Array mit mehreren Ansichtstypen in Swift / SwiftUI

Aug 18 2020

Ich möchte eine Ansicht erstellen, in der Benutzer ihr bevorzugtes Hintergrundbild auswählen können. Dazu gehört ein Rechteck mit einer Vordergrundfarbe oder ein Bild. Bisher muss das funktionieren, indem ich es erstelle

Struktur:

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

Ich füge sie einem Array wie diesem hinzu

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

Dadurch kann ich ein Array von BackgroundImages wie dieses durchlaufen

Aussicht:

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

Ich kann jedoch nicht hinzufügen

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

für die Bilder, da AnyView dies nicht zulässt.

Gibt es einen besseren Weg, dies zu erreichen? Sollte ich stattdessen nur zwei separate Arrays für Shapes / Images haben? Oder gibt es eine alternative View-Struktur, die dafür besser geeignet wäre?

Vielen Dank!

Antworten

1 vacawama Aug 18 2020 at 19:51

Wie @ DávidPásztor in den Kommentaren erwähnt hat, ist es ein schlechtes Design, Ansichten in Ihrem ViewModel zu speichern.

Wirklich müssen Sie nur eine Farbe und einen Bildnamen speichern. Lassen Sie den View Building Code die tatsächlichen Ansichten erstellen.

Hier ist eine mögliche Implementierung.

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

Hinweis: Ich habe überlegt, ein enumzu verwenden, um die Unterscheidung zwischen imageName und Farbe zu speichern, aber es war am einfachsten, nur Optionen zu verwenden und das zu speichern, das ich benötigte. Mit der Schnittstelle, die für den Code zum Erstellen von Ansichten verfügbar ist, können Sie die Implementierung problemlos so ändern, wie Sie die Informationen speichern möchten.