How to build a View in SwiftUI that displays different type of data models in a polymorphic-like way?

42 Views Asked by At

Trying SwiftUI again after a few years ... I have some difficulties mapping my UI idea to SwiftUI, maybe thinking too much in an OOP way?

I want a list of different data models to be rendered by their corresponding views and displayed in a vertical grid on my main view.

I tried having data fed into models, that would then be mapped to construct component views depending on their type, and finally delivered to a main view, to display in a 2 columns list ...

After some trials and errors, I settled for models that generate their custom view which is then used by my main view ...

I am not entirely satisfied with my current architecture and would like to know if there is a more colloquial and better way to achieve the same thing with the latest version of SwiftUI.

Here is a simplified representation of my UI:

  1. the base model "abstract class"
import SwiftUI

class BaseModel: Identifiable, Equatable {
    static func == (lhs: BaseModel, rhs: BaseModel) -> Bool {
        lhs.id == rhs.id    }
    
    var id : String
    var isSelected: Bool = false 
    init(_ id: String = "") {
        self.id = id
    }
    func toggleSelection() {
        self.isSelected = !self.isSelected
    }
    func view() -> any View {
        return EmptyView()
    }
}
  1. one type of model that displays text
import SwiftUI

class TextModel: BaseModel {
    let text: String
    override init(_ text: String) {
        self.text = text
        super.init(self.text.description)
    }
    override func view() -> any View {
        return Button(action: self.toggleSelection) {
            Text(self.text)
                .frame( maxWidth: .infinity,maxHeight: .infinity)
                .padding()
                .foregroundStyle(self.isSelected ? .white : .blue)
                .background(self.isSelected ? .blue : .white)
                .frame(width:.infinity)
                .cornerRadius(10)
                .overlay(
                        RoundedRectangle(cornerRadius: 10).stroke(Color(.blue), lineWidth: 2)
                    )
        }
    }
}
  1. another type of model that displays images
import SwiftUI

class ImageModel: BaseModel {
    let resource: String
    override init(_ resource: String) {
        self.resource = resource
        super.init(self.resource.description)
    }
    override func view() -> any View {
        return Button(action: self.toggleSelection) {
            Image(self.resource)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(maxWidth: .infinity, alignment: .center)
                .clipped()
                .padding()
                .foregroundStyle(self.isSelected ? .white : .blue)
                .background(self.isSelected ? .blue : .white)
                .frame(width:.infinity)
                .cornerRadius(10)
                .overlay(
                        RoundedRectangle(cornerRadius: 10).stroke(Color(.blue), lineWidth: 2)
                    )
        }
    }
}
  1. the main view
import SwiftUI

struct MainView: View {
    var models:[BaseModel] = [
        TextModel("ABCDEFG"),
        ImageModel("my_image")
    ]

    var body: some View {
        VStack {
                LazyVGrid(columns: [
                    GridItem(.flexible()), GridItem(.flexible())
                ]) {
                    ForEach(self.models) {model in
                        AnyView(model.view()
                            .frame(maxWidth: .infinity)).frame(maxWidth: .infinity)
                    }
                }
            }
        }.padding()
    }
}
0

There are 0 best solutions below