'SwftUI List storing style in the model
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.
Solution 1:[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)
}
}
Solution 2:[2]
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)
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 |
