0

I have an app that will play a confirmation tone if a toggle switch is set. Because the tone may be played for different actions throughout the app, I store the toggle status in the SoundManager class.

I am updating this code from ObservableObject to @Observable. (There were no errors in the ObservableObject version.) When I build the new code for @Observable, the error Cannot find '$sound' in scope appears at the UtilView toggle switch. If I remove the dollar sign I get the error Cannot convert value 'soundStatus' of type 'Bool' to expected type 'Binding', use wrapper instead. Insert $. In either case, I still have an error.

Several examples on Stackoverflow (pre @Observable) with the same error for the binding suggested using .wrappedValue or .enumberated(). It doesn't seem like I can use a standard binding since I'm checking the toggle status in multiple places throughout the app.

Here is the piece of code that activated the confirmation tone:

@Environment(SoundManager.self) private var sound


      if sound.soundStatus {
           sound.playSound(sound: .confirmTone)
      }
struct UtilView: View {
    
    @Environment(SoundManager.self) private var sound
    
    var body: some View {
        
        VStack (alignment: .leading) {
            
            List {
                // menu here
                
                   
                Toggle(isOn: $sound.soundStatus,     // <– Error here
                       label: { Text("App Sound Confirmations") }
                )
                .toggleStyle(SwitchToggleStyle())
            }
        }
        .listStyle(.plain)
    }
}

This is the soundManager class

import AVKit
import SwiftUI

@Observable class SoundManager {
// class SoundManager: ObservableObject {
    
    @AppStorage(StorageKeys.sound.rawValue) var soundStatus: Bool = false
        
    var player: AVAudioPlayer?
    var session: AVAudioSession = .sharedInstance()
    
    func playSound(sound: SoundOption) {
        
        guard let url = Bundle.main.url(forResource: sound.rawValue, withExtension: ".wav") else { return }
        
        do {
            try session.setActive(false)
            try session.setCategory(.playback)
            player = try AVAudioPlayer(contentsOf: url)
            player?.setVolume( 0.2, fadeDuration: 0)
            try session.setActive(true)
            player?.play()
        } 
            catch let error {
                
            #if DEBUG
            print("Error playing sound. \(error.localizedDescription)")
            #endif
        }
    }
}

0

1 Answer 1

1

The @Observable class SoundManager cannot observe @AppStorage directly. Based on this post Swift: Ist there any way using @AppStorage with @Observable? you could use the following example code.

Note also the important use of @Bindable var sound = sound to remove the error Cannot find '$sound' in scope.

See also Managing model data in your app for how to use Bindable.

struct ContentView: View {
    @State private var sound = SoundManager() // <--- here
    
    var body: some View {
        UtilView()
            .environment(sound) // <--- here
    }
}

struct UtilView: View {
    @Environment(SoundManager.self) private var sound
    
    var body: some View {
        @Bindable var sound = sound // <--- here
        VStack (alignment: .leading) {
            Text("soundStatus: \(sound.soundStatus)") // <--- for testing
            List {
                // menu here
                Toggle(isOn: $sound.soundStatus,
                       label: { Text("App Sound Confirmations") }
                )
                .toggleStyle(SwitchToggleStyle())
            }
        }
        .listStyle(.plain)
    }
}


@Observable class SoundManager {

    // --- here
    var soundStatus: Bool {
        get {
            access(keyPath: \.soundStatus)
            return UserDefaults.standard.bool(forKey: "sound")  // StorageKeys.sound.rawValue
        }
        set {
            withMutation(keyPath: \.soundStatus) {
                UserDefaults.standard.setValue(newValue, forKey: "sound")  // StorageKeys.sound.rawValue
            }
        }
    }
    // ....
}
1
  • Thanks for the link to the @Observable article. I read the update article but missed the overall discussion of Observable. Commented Jun 28 at 2:05

Not the answer you're looking for? Browse other questions tagged or ask your own question.