0

I am storing the list's style into the ViewModel as shown in the code below.


class Model: ObservableObject {
    @Published var items = ["A", "B", "C", "D"]
    @Published var currentStyle = PlainListStyle()
}


struct ContainerView: View {
    @ObservedObject var model = Model()

    var body: some View {
        VStack {
            Spacer()
            List {
                ForEach(model.items, id: \.self) { item in
                    Text(item)
                }
            }
            .listStyle(model.currentStyle)
        }
        .background(.green)
    }
}

My problem is how to pass the current style as parameter of the ViewModel init function.

I would like to have something like:


class Model: ObservableObject {
    @Published var items = ["A", "B", "C", "D"]
    @Published var currentStyle: ?

    init(currentStyle: ?) {
        self.currentStyle = currentStyle
    }

    func changeToStyle(newStyle: ?) {
        self.currentStyle = newStyle
    }
}

I tried this:

class Model<S: ListStyle>: ObservableObject {
    @Published var items = ["A", "B", "C", "D"]
    @Published var currentStyle: S

    init(currentStyle: S) {
        self.currentStyle = currentStyle
    }
//
//    func changeStyle(newStyle: S) {
//        self.currentStyle = newStyle
//    }
    
}

which allows to configure the style at model creation but not to change the style later.

Fab
  • 1,468
  • 1
  • 16
  • 37
  • initing an object with @ObservedObject is a mistake. And we don't use view models in SwiftUI. See this "MVVM has no place in SwiftUI" https://stackoverflow.com/a/60883764/259521 – malhal Mar 10 '22 at 12:47
  • @malhal: I don't think! Master, show me the right way then :) – Fab Mar 10 '22 at 14:01
  • Also `id: \.self` is a big mistake. Ok I posted an answer showing you the way ;) – malhal Mar 10 '22 at 14:13

2 Answers2

1

You can create an enum that will be the type of the property currentStyle, and handle the view with a customised modifier.

An example - here's how the model would look like:

class Model: ObservableObject {
    @Published var items = ["A", "B", "C", "D"]
    @Published var currentStyle = MyListStyle.inset

    init(currentStyle: MyListStyle) {
        self.currentStyle = currentStyle
    }
    
    enum MyListStyle {
        case plain, grouped, inset
    }
}

Here's the modifier you create:

extension View {    
    @ViewBuilder
    func myListStyle(type: Model.MyListStyle) -> some View {
        switch type {
        case .plain:
            self.listStyle(.plain)
        case .grouped:
            self.listStyle(.grouped)
        case .inset:
            self.listStyle(.inset)
        }
    }
}

And here's how you use it (I added a Button to test it):

struct Example: View {

    // EDIT: Correcting your code to avoid initialising an @ObservedObject, as highlighted in the comments
    @StateObject var model = Model(currentStyle: .plain)

    var body: some View {
        VStack {
            
            // This Button just for testing
            Button {
                if model.currentStyle == .plain {
                    model.currentStyle = .grouped
                } else if model.currentStyle == .grouped {
                    model.currentStyle = .inset
                } else {
                    model.currentStyle = .plain
                }
            } label: {
                Text("Change style")
            }
            
            Spacer()
            List {
                ForEach(model.items, id: \.self) { item in
                    Text(item)
                }
            }
            
            // Here's the modifier that changes the type of the List
            .myListStyle(type: model.currentStyle)
        }
        .background(.green)
    }
}
HunterLion
  • 3,496
  • 1
  • 6
  • 18
0

You asked me to show you the right way, here it is ;)

struct Item: Identifiable {
    let id = UUID()
    var title: String
}

class Model: ObservableObject {
    @Published var items = [Item(title: "A"), Item(title:"B"),Item(title: "C"), Item(title: "D")]

    static let shared = Model()
    static let preview = Model()
}


@main
struct MyApp: App {
    let model = Model.shared

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(model)
        }
    }
}


struct ContentView: View {
    @EnvironmentObject model: Model
    @State var currentStyle = PlainListStyle()

    var body: some View {
        VStack {
            Spacer()
            List {
                ForEach(model.items) { item in
                    Text(item.title)
                }
            }
            .listStyle(currentStyle)
        }
        .background(.green)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(Model.preview)
    }
}
malhal
  • 26,330
  • 7
  • 115
  • 133