admin管理员组

文章数量:1182775

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

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()
}
Share Improve this question edited Jan 26 at 22:34 HangarRash 15k5 gold badges19 silver badges55 bronze badges asked Jan 26 at 19:20 AnsilansAnsilans 31 bronze badge New contributor Ansilans is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct. 9
  • You need to use @State for the settingsViewModel property – Joakim Danielson Commented Jan 26 at 19:24
  • stackoverflow.com/questions/77675493/… – lorem ipsum Commented Jan 26 at 19:27
  • @JoakimDanielson you are right. I thought the fact that the object is already observable, I would not need the State tag for the ViewModel. Thanks! – Ansilans Commented Jan 26 at 20:11
  • It also needs to be struct not class – malhal Commented Jan 26 at 20:58
  • 3 @malhal That's not true, since @Observable macro relies on reference semantics to track changes to properties, which is a behavior inherent to classes and not structs. Also, you would get an error if trying to apply the @Observable macro to a struct - Documentation for Observation framework – Andrei G. Commented Jan 26 at 21:25
 |  Show 4 more comments

1 Answer 1

Reset to default 0

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

本文标签: