2

I want to create a view where people can choose their preferred background image, these include a rectangle with a foreground colour or an image. So far I've got this to work by creating this

Struct:

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

I am adding them to an array like so

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

which allows me to loop through an array of BackgroundImages like so

View:

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

However I am unable to add

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

for the images as AnyView doesn't allow this.

Is there a better way of achieving this? Should I just have two separate arrays for Shapes/Images instead? Or is there an alternate View struct that would be better suited this?

Thanks!

2
  • 1
    A viewmodel storing actual views is a very bad design. Your business logic and UI should be separated in a way that only the view can access your business logic (view models), but the view models have no knowledge of or access to the UI. Commented Aug 18, 2020 at 10:25
  • @DávidPásztor Thanks for the insight, how would you suggest reorganising this logic? Commented Aug 18, 2020 at 10:54

1 Answer 1

1

As @DávidPásztor mentioned in the comments, it is a bad design to store Views in your ViewModel.

Really you just need to store a color and an image name. Let the view building code construct the actual views.

Here is a possible implementation.

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

Note: I thought of using an enum to store the distinction between imageName and color, but it was simplest to just use optionals and store the one I needed. With the interface that is being exposed to the view building code, you could easily change the implementation to however you'd like to store the information.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.