Search code examples
swiftswiftuiuilocalnotification

SwiftUI: How can I make a notification interaction change part of my view?


I'm new to SwiftUI and would like some help. I'm building an app and I want the user to interact with a notification. If they "Okay" the notification, I want it to change part of my view, in this case just a counter that goes up with every "Okay". The problem I have is that I can't @State var notificationCounter outside a view, so it doesn't update the view automatically. However, when I input @State var notificationCounter into the view, my func userNotificationCenter can't access the variable due to scope. How can I work around this?

Here's what I have so far:

import SwiftUI
import UserNotifications

var notificationCounter = 0
struct HomeScreenView: View {
//    @State var notificationCounter = 0
    @State var delegate = NotificationDelegate()
    var body: some View {
            ZStack {
            Color("Nice Green")
                .ignoresSafeArea()
            VStack {
                Button(action:
                        createNotification, 
                       label: {
                    Text("Notify User")
                })
                .onAppear(perform: { //request permissions
                    UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.badge,.sound]) {(_,_) in
                    }
                    
                    UNUserNotificationCenter.current().delegate = delegate
                })
                Text("Notification Interactions \(notificationCounter)")
            }
                }
    }
    
    func createNotification() {
        let content = UNMutableNotificationContent()
        content.title = "God of Posture"
        content.subtitle = "Straighten Your Neck"
        content.categoryIdentifier = "Actions"
        
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 4, repeats: false)
        let request = UNNotificationRequest(identifier: "In-App", content: content, trigger: trigger)
        
        //notification actions
        let close = UNNotificationAction(identifier: "Close", title: "Close", options: .destructive)
        let okay = UNNotificationAction(identifier: "Okay", title: "Okay", options: .destructive)
        let category = UNNotificationCategory(identifier: "Actions", actions: [close, okay], intentIdentifiers: [], options: [])
        
        UNUserNotificationCenter.current().setNotificationCategories([category])
        
        UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
    }
    




struct HomeScreenView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().previewDevice(PreviewDevice(rawValue: "iPhone X"))
    }
}
}

class NotificationDelegate: NSObject, ObservableObject, UNUserNotificationCenterDelegate {

    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        
        completionHandler([.badge, .banner, .sound])
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
         
        if response.actionIdentifier == "Okay" {
            print("Hello")
            print("counter is " + String(notificationCounter))
            notificationCounter = notificationCounter + 1
            print("counter is now " + String(notificationCounter))
        }
        completionHandler()
        
    }
}

Solution

  • The easiest solution is to make a @Published property on your NotificationDelegate (which should be @StateObject, not @State, by the way).

    struct HomeScreenView: View {
        @StateObject var delegate = NotificationDelegate()
        
        var body: some View {
            ZStack {
                Color("Nice Green")
                    .ignoresSafeArea()
                VStack {
                    Button(action: delegate.createNotification) {
                        Text("Notify User")
                    }
                    .onAppear {
                        delegate.requestAuthorization()
                    }
                    Text("Notification Interactions \(delegate.notificationCounter)")
                }
            }
        }
    }
    
    class NotificationDelegate: NSObject, ObservableObject, UNUserNotificationCenterDelegate {
        
        @Published var notificationCounter = 0
        
        override init() {
            super.init()
            UNUserNotificationCenter.current().delegate = self
        }
        
        func requestAuthorization() {
            UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.badge,.sound]) {(_,_) in
            }
        }
        
        func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
            
            completionHandler([.badge, .banner, .sound])
        }
        
        func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
            
            if response.actionIdentifier == "Okay" {
                print("Hello")
                print("counter is " + String(notificationCounter))
                notificationCounter = notificationCounter + 1
                print("counter is now " + String(notificationCounter))
            }
            completionHandler()
        }
        
        func createNotification() {
            let content = UNMutableNotificationContent()
            content.title = "God of Posture"
            content.subtitle = "Straighten Your Neck"
            content.categoryIdentifier = "Actions"
            
            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 4, repeats: false)
            let request = UNNotificationRequest(identifier: "In-App", content: content, trigger: trigger)
            
            //notification actions
            let close = UNNotificationAction(identifier: "Close", title: "Close", options: .destructive)
            let okay = UNNotificationAction(identifier: "Okay", title: "Okay", options: .destructive)
            let category = UNNotificationCategory(identifier: "Actions", actions: [close, okay], intentIdentifiers: [], options: [])
            
            UNUserNotificationCenter.current().setNotificationCategories([category])
            
            UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
        }
    }