I am not entire sure why using the @Observable tag does not work the same as using the 'old' way which is using @Published and ObservableObject and @StateObject. I am just curious why that wouldn't work since using @Observable is supposed to be the new way.
Also I am new to SwiftUI.
I am trying to get the Picker from the view screen to update the variable currentSelection
from SettingsViewModel on any user interactions with the picker.
import Foundation
@Observable
class SettingsViewModel {
var currentSelection : ColorOptionEnum
var dataHandler : DataHandler
init() {
let localdataHandler: DataHandler = .init()
currentSelection = localdataHandler.currentBibleVersion
dataHandler = localdataHandler
}
}
-------------
import SwiftUI
struct SettingsView: View {
var settingsViewModel : SettingsViewModel = .init()
var body: some View {
Form {
Text("Mode")
Picker(
"Mode",
selection: $settingsViewModel.currentSelection // GETTING Error here ( cannot find '$settingsViewModel' in scope)
) {
Text(
"Green"
)
.tag(
ColorOptionEnum.Green
)
Text(
"Blue"
)
.tag(
ColorOptionEnum.Blue )
}
}
}
}
#Preview {
SettingsView()
}
This version works
import Foundation
class SettingsViewModel: ObservableObject {
@Published var currentSelection : ColorOptionEnum
var dataHandler : DataHandler
init() {
let localdataHandler: DataHandler = .init()
currentSelection = localdataHandler.currentBibleVersion
dataHandler = localdataHandler
}
}
-------------
import SwiftUI
struct SettingsView: View {
@StateObject var settingsViewModel : SettingsViewModel = .init()
var body: some View {
Form {
Text("Mode")
Picker(
"Mode",
selection: $settingsViewModel.currentSelection // No error
) {
Text(
"Green"
)
.tag(
ColorOptionEnum.Green
)
Text(
"Blue"
)
.tag(
ColorOptionEnum.Blue
)
}
}
}
}
#Preview {
SettingsView()
}
The error cannot find '$settingsViewModel' in scope
is because Picker
requires a binding for its selection parameter. When you use a var settingsViewModel : SettingsViewModel = .init()
, you don't get a binding to your model.
While @State
does get you a binding, it should only be used if it's considered to be the owner of the object. Otherwise, if the owner is a parent view, you can accept it as a Binding
in your child view or when using @Observable
, you can accept it as an object and get a binding to it in your child view, using @Bindable
.
Note: Since your code wasn't reproducible due to lacking the DataHandler
class and the ColorOptionEnum
, I commented it out in the example code below and added a sample enum to make it work:
import Foundation
import SwiftUI
enum ColorOptionEnum {
case green, blue, orange // <- cases should be lowercased
var color: Color {
switch self {
case .green: return .green
case .blue: return .blue
case .orange: return .orange
}
}
}
@Observable
class DemoSettings {
var currentSelection : ColorOptionEnum = .green
// var dataHandler : DataHandler
// init() {
// let localdataHandler: DataHandler = .init()
// currentSelection = localdataHandler.currentBibleVersion
// dataHandler = localdataHandler
// }
}
//Parent view
struct DemoSettingsRootView: View {
//State value
@State private var demoSettings = DemoSettings() // <- Parent view owns the settings object
//Body
var body: some View {
DemoSettingsView(settings: demoSettings) // <- Settings passed to child view as an (observable) object
}
}
//Child view
struct DemoSettingsView: View {
//Parameters
var settings : DemoSettings // <- settings accepted from parent as an object
//Body
var body: some View {
@Bindable var settings = settings // <- Here, get a binding to the observable settings object
Text("Selected mode: \(String(describing: settings.currentSelection).capitalized)")
.foregroundStyle(settings.currentSelection.color)
Form {
Picker("Mode", selection: $settings.currentSelection) { // <- use the binding for the selection parameter
Text("Green")
.tag(ColorOptionEnum.green)
Text("Blue")
.tag(ColorOptionEnum.blue)
}
}
}
}
#Preview {
DemoSettingsRootView()
}