Tableau comprenant plusieurs types de vues dans Swift / SwiftUI

Aug 18 2020

Je souhaite créer une vue dans laquelle les gens peuvent choisir leur image d'arrière-plan préférée, notamment un rectangle avec une couleur de premier plan ou une image. Jusqu'à présent, je dois cela fonctionner en créant ceci

Struct:

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

Je les ajoute à un tableau comme ça

VoirModèle:

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

ce qui me permet de parcourir un tableau de BackgroundImages comme ceci

Vue:

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

Cependant, je ne peux pas ajouter

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

pour les images car AnyView ne le permet pas.

Y a-t-il une meilleure façon d'y parvenir? Dois-je avoir deux tableaux séparés pour les formes / images à la place? Ou existe-t-il une autre structure de vue qui conviendrait mieux à cela?

Merci!

Réponses

1 vacawama Aug 18 2020 at 19:51

Comme @ DávidPásztor l'a mentionné dans les commentaires, stocker des vues dans votre ViewModel est une mauvaise conception.

En réalité, il vous suffit de stocker une couleur et un nom d'image. Laissez le code du bâtiment de vue construire les vues réelles.

Voici une implémentation possible.

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

Remarque: j'ai pensé à utiliser un enumpour stocker la distinction entre imageName et couleur, mais il était plus simple d'utiliser simplement des options et de stocker celle dont j'avais besoin. Avec l'interface qui est exposée au code du bâtiment de vue, vous pouvez facilement modifier l'implémentation comme vous souhaitez stocker les informations.