Matriz incluindo vários tipos de visualização em Swift / SwiftUI

Aug 18 2020

Quero criar uma visualização onde as pessoas possam escolher sua imagem de fundo preferida, que inclui um retângulo com uma cor de primeiro plano ou uma imagem. Até agora eu tenho que trabalhar criando este

Struct:

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

Estou adicionando-os a uma matriz assim

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

o que me permite percorrer uma série de BackgroundImages como

Visão:

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

No entanto, não consigo adicionar

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

para as imagens, pois o AnyView não permite isso.

Existe uma maneira melhor de conseguir isso? Devo ter apenas duas matrizes separadas para formas / imagens em vez disso? Ou existe uma estrutura de visualização alternativa que seria mais adequada para isso?

Obrigado!

Respostas

1 vacawama Aug 18 2020 at 19:51

Como @ DávidPásztor mencionou nos comentários, é um design ruim armazenar Views em seu ViewModel.

Na verdade, você só precisa armazenar uma cor e um nome de imagem. Deixe o código de construção de visualizações construir as visualizações reais.

Aqui está uma implementação possível.

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

Nota: Pensei em usar um enumpara armazenar a distinção entre imageName e color, mas era mais simples usar apenas opcionais e armazenar o que eu precisava. Com a interface que está sendo exposta ao código de construção da visualização, você pode facilmente alterar a implementação da maneira que quiser para armazenar as informações.