Home > Software design >  How to receive application's notifications using SwiftUI
How to receive application's notifications using SwiftUI

Time:07-06

Back in the day, I remember finding a code that could listen to any notification circulating internally on macOS. I am not sure if that was Swift, Objective-C or whatever.

Is there something that can be used on Swift with that purpose?

I have tried the delegate

func application(_ application: NSApplication, didReceiveRemoteNotification userInfo: [String : Any]) {
  print(userInfo)
}

But it did not receive anything.

Any way to do that?

CodePudding user response:

If there is no direct support in SwiftUI for your specific notification, you can still use an AppDelegate.

Once you have an AppDelegate you can catch the notifications.

Then, there are several approaches how you then propagate the notification to a SwiftUI view. For example, you may use a custom environment value which you set with the notification value. However, that would require you to explicitly create your views using a HostingViewController - i.e you would not declare a SwiftUI App anymore (aka struct MyApp: App {...}) which makes your shiny app much less elegant.

Or, you might use a separate model object which keeps track of the notification values and their respective changes:

Create a class (here: MyObservedState, to give it a name) which conforms to ObservableObject whose published value corresponds to the notification value. Change that value in the App Delegate method. Next, use that Observable Object instance as an @EnvironmentObject in views.

Below is a sketch - not a complete working example - how you could accomplish it.

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            MainView()
        }
    }
}

class AppDelegate: NSObject, UIApplicationDelegate {
   // add your delegate methods
   ...
}

Create the model class which we called MyObservedState, which conforms to ObservableObject and whose published value corresponds to your notification value. Hint: You might use an enum with associated values.

(not shown)

Next, in an ancestor view, ensure you inject the object MyObservedState into the SwiftUI environment:

struct MainView: View {
    var body: some View {
        MyView()
        .environmentObject(MyObservedState.shared)
    }
}

Next, reference the MyObservedState environment object in any View and react to its changes like below:

struct MyView: View {
    @EnvironmentObject private var myState: MyObservedState

    var body: some View {
        NavigationView {
            ContentView()
        }
        .onChange(of: myState) { newValue in
             doSomething(newValue)
        }
    }
}

Caution: there are a few catches and caveats which depend on how you actually try to solve your problem. ;)

Caveat (one of):

Environment values will not be propagated through to PresentationHostingControllers and possibly also not to any other HostingController. So, you have to inject environment values and objects explicitly for example for SwiftUI "sheets" where you first grab the environment value from the parent and then inject it per environment or environmentObject modifier applied to the modal view.

  • Related