4

I've got a variable (a label which describes a play) within a class that I need to pass around between my views, which I do through the @EnvironmentObject state. When a function (in the same class as the variable) that changes that label gets called by one view the variable is updated in the other views. However, that function is also called by the AppDelegate when a notification is fired. At the moment, I've got the class containing the label declared as a new instance in the AppDelegate, which results in no changes to the variable in the view/struct.

Is it possible to give the AppDeleagte access to the environment object (e.g. through AppDelegate().environmentobject(myClass), if so where?) or is there a better way to do this?

Simplified Code:

Class which contains the playlistLabel and the the function to change the playlist and the label

class MusicManager: NSObject, ObservableObject {

    var playlistLabel: String = ""

    func playPlaylistNow(chosenPlaylist: String?) {  
        playlistLabel = "Playlist: \(chosenPlaylist!)"
    }

}

Home View which displays the label

struct HomeView: View {

    @EnvironmentObject var musicManager: MusicManager

    var body: some View {

        Text(musicManager.playlistLabel)

    }

}

AppDelegate

class AppDelegate: UIResponder, UIApplicationDelegate, AVAudioPlayerDelegate {

    var musicManager: MusicManager = MusicManager()

        func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
            var playlistName: String = ""
            if let userInfo = notification.userInfo {
                playlistName = userInfo["soundName"] as! String
            }
            musicPlayerManager.playPlaylist(chosenPlaylist: playlistName)

        }
    }
Asperi
  • 228,894
  • 20
  • 464
  • 690
Jack Vanderpump
  • 216
  • 2
  • 16

2 Answers2

7

Here is possible approach. Assuming your AppDelegate have property like

var musicManager: MusicManager?

in your SceneDelegate, where I suppose you create HomeView, you can have the following code

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

    let musicManager = MusicManager()
    if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
        addDelegate.musicManager = musicManager
    }
    let contentView = HomeView().environmentObject(musicManager)
    ...

thus both AppDelegate and HomeView have access to same instance of MusicManager.

Or vise versa

...
var musicManager: MusicManager?
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
    musicManager = addDelegate.musicManager
}
let contentView = HomeView().environmentObject(musicManager ?? MusicManager())
...

depending of what is preferable.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
4

A better approach to solve these kind of problems wherein you need to make the same instance globally available you should use Singleton design pattern. Also, according to best coding practices we should refrain from overloading AppDelegate with multiple responsibilities and variables. It is always better to decouple your code by dividing responsibility.

class MusicManager: NSObject, ObservableObject {
   let sharedMusicManager = MusicManager()
   var playlistLabel: String = ""

   private init() {}

  func playPlaylistNow(chosenPlaylist: String?) {  
    playlistLabel = "Playlist: \(chosenPlaylist!)"
  }
}

AppDelegate

 class AppDelegate: UIResponder, UIApplicationDelegate, AVAudioPlayerDelegate {

     func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
        var playlistName: String = ""
        if let userInfo = notification.userInfo {
            playlistName = userInfo["soundName"] as! String
        }
        sharedMusicManager.playPlaylist(chosenPlaylist: playlistName)
    }
}

Similarly you can update the variable from other views. Keeping a private init() would insure that no other instance of that class is creating again. Also, it will always display the most updated value.

Priyal
  • 879
  • 1
  • 9
  • 30
  • 1
    I much prefer this approach to the use of environment objects - it seems cleaner not feeding an environment object through all my subviews. I had to add this line at top of AppDelegate *let sharedMusicManager = MusicManager.sharedMusicManager* and had to refer to *static let sharedMusicManager = MusicManager()* at the beginning of the MusicManager class, but has subsequently worked perfectly. Thank you! – Jack Vanderpump Dec 17 '19 at 20:29
  • @Priyal how can you access the sharedMusicManager with in the AppDelegate. It seems like you're just randomly accessing a variable of a class. This doesn't work. Where exactly are you putting the MusicManager class? – Joshua Segal Jun 18 '20 at 19:52
  • @JoshuaSegal Musicmanager is a separate class in a separate file. I am not using any variable of a class. I am accessing the shared instance of MusicManager class. – Priyal Jul 22 '20 at 09:46