Search code examples
swiftswiftuivisionos

VisionOS send notification


How does one send notification in vision os? I seem to not be able to find any information about notifications in vision os.

I have a window which after some time it should alert the user to look at it and I thought notification is a good idea (or is there some other way I could do that?) but I can't get it to work.

I have the permission requested and granted. Am I doing something wrong? Is vision os not supporting notifications? I can't find anything related to this. Did anyone manage to send a notification and if yes, how?

I tried the following code and it does not do anything:

let content = UNMutableNotificationContent()
content.title = "Feed the cat"
content.body = "It looks hungry"
content.sound = UNNotificationSound.default

let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)

let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.add(request)

Solution

  • You are missing the delegate and telling the UNUserNotificationCenter how to present the notification in the foreground.

    extension NotificationManager:  UNUserNotificationCenterDelegate {
        
        func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
            .banner
        }
    }
    

    The rest of the manager would look something like.

    @Observable final class NotificationManager: NSObject {
        private(set) var isPermissionGranted = false
        let userNC = UNUserNotificationCenter.current()
        
        override init() {
            super.init()
            userNC.delegate = self
        }
        
        func sendNotifications() async throws {
            let id = UUID().uuidString
            
            let content = UNMutableNotificationContent()
            content.title = "Let's feed the cat"
            content.subtitle = "It looks hungry"
            
            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: true)
            let request = UNNotificationRequest(identifier: id,content: content,trigger: trigger)
            try await userNC.add(request)
            print("\(#function) :: scheduled")
        }
        
        func requestAuthorization () async throws {
            do {
                isPermissionGranted = try await userNC.requestAuthorization(options: [.alert, .badge, .sound])
            } catch {
                print(error)
            }
        }
        
        func cancelAll() {
            userNC.removeAllPendingNotificationRequests()
        }
    }
    

    Note that you should create the manager at the very top of your app with

    @State private var manager: NotificationManager = .init()
    

    and inject it into the app with

    .environment(manager)
    

    So you can access it everywhere with

    @Environment(NotificationManager.self) private var manager
    

    It is important that the delegate is stable so the app doesn't miss notifications.

    Here is a sample View

    import SwiftUI
    import RealityKit
    import UserNotifications
    
    struct NotificationSample: View {
        @Environment(NotificationManager.self) private var manager
        @State private var date: Date?
        @State private var isScheduling: Bool = false
        var body: some View {
            
            RealityView { content, attchments in
                guard let attachment = attchments.entity(for: "Feed") else {return}
                content.add(attachment)
            } update: { content, attchments in
                
            } attachments: {
                Attachment(id: "Feed") {
                    VStack {
                        if let date {
                            Text(date, style: .timer)
                        }
                        Button("FEED THE CAT") {
                            isScheduling.toggle()
                        }.task(id: isScheduling) {
                            guard isScheduling else {return}
                            do {
                                try await manager.sendNotifications()
                            } catch {
                                print(error)
                            }
                            isScheduling = false
                            date = Calendar.current.date(byAdding: .minute, value: 1, to: Date())
                        }.disabled(!manager.isPermissionGranted)
                    }
                }
            }
            .task {
                do {
                    try await manager.requestAuthorization()
                } catch {
                    print(error)
                }
                manager.cancelAll()
            }
            
        }
    }