2

I want to simply get the list of local notifications that are scheduled and populate a SwiftUI List using forEach. I believe it should work like I have done below, but the array is always empty as it seems to be used before the for loop is finished. I tried the getNotifications() function with a completion handler, and also as a return function, but both ways still didn't work. How can I wait until the for loop is done to populate my list? Or if there is another way to do this please let me know, thank you.

var notificationArray = [UNNotificationRequest]()

func getNotifications() {
        
print("getNotifications")
        center.getPendingNotificationRequests(completionHandler: { requests in
            for request in requests {
                print(request.content.title)
                notificationArray.append(request)
            }
        })

}

struct ListView: View {

var body: some View {
    
    NavigationView {
    List {
        ForEach(notificationArray, id: \.content) { notification in

            HStack {
                VStack(alignment: .leading, spacing: 10) {
                    let notif = notification.content
                    Text(notif.title)
                    Text(notif.subtitle)
                    .opacity(0.5)
            }
                    }
        
    }

}
 .onAppear() {
        getNotifications()
    }
}
}

Update:

Here is how I am adding a new notification and calling getNotifications again. I want the list to dynamically update as the new array is made. Printing to console shows that the getNotifications is working correctly and the new array contains the added notiication.

Section {
                Button(action: {
                    print("Adding Notification: ", title, bodyText, timeIntValue[previewIndex])
                    addNotification(title: title, bodyText: bodyText, timeInt: timeIntValue[previewIndex])
                    showDetail = false
                    self.vm.getNotifications()
                }) {
                    Text("Save Notification")
                }
            }.disabled(title.isEmpty || bodyText.isEmpty)
Peter Ruppert
  • 1,067
  • 9
  • 24

1 Answers1

2

Your global notificationArray is not observed by view. It should be dynamic property... possible solution is to wrap it into ObservableObject view model.

Here is a demo of solution:

class ViewModel: ObservableObject {
  @Published var notificationArray = [UNNotificationRequest]()

  func getNotifications() {
        
    print("getNotifications")
        center.getPendingNotificationRequests(completionHandler: { requests in
            var newArray = [UNNotificationRequest]()
            for request in requests {
                print(request.content.title)
                newArray.append(request)
            }
            DispatchQueue.main.async {
              self.notificationArray = newArray
            }
        })
  }
}

struct ListView: View {
  @ObservedObject var vm = ViewModel()
//@StateObject var vm = ViewModel()    // << for SwiftUI 2.0

  var body: some View {
    
    NavigationView {
      List {
        ForEach(vm.notificationArray, id: \.content) { notification in
            HStack {
                VStack(alignment: .leading, spacing: 10) {
                    let notif = notification.content
                    Text(notif.title)
                    Text(notif.subtitle)
                    .opacity(0.5)
                }
            }
      }
   }
   .onAppear() {
        self.vm.getNotifications()
    }
 }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • This is working better, and shows the list when I open the app, but unfortunately it when I add a new notification and call `self.vm.getNotifications()` again it doesn't update my list to reflect that. Am I missing something? I added the `@StateObject var vm = ViewModel()` to each `View` that needs it. Adding how I add a notificaiton to the question. – Peter Ruppert Oct 26 '20 at 11:28
  • No, if it is the same view model then it should be created once, at some root view, and inject for hierarchy subviews as environment object. Then all will use same instance and being updated in one place will update all dependent views. – Asperi Oct 26 '20 at 12:07
  • I changed the stateObject to enviromental object like so: `@EnvironmentObject var vm: ViewModel` but now get an error when the list populates `Thread 1: Fatal error: No ObservableObject of type ViewModel found. A View.environmentObject(_:) for ViewModel may be missing as an ancestor of this view.` atm how the heirarchy works is my main `ContentView` contains the `ListView` but only shows it if `getNotifications` returns a non empty `notificationsArray` then I want the `ListView` to use the array and populate the list. and after this I want I should be able to update the array and the list shld – Peter Ruppert Oct 26 '20 at 13:04
  • Okay, I understand now and got it working. in my subviews I added the `environmentObject` variable: `@EnvironmentObject var vm: ViewModel` and then on my main view when I call the sub view I did like so: `ListView().environmentObject(vm)` Now I can call `vm.getNotifications()` and the List updates accordingly. – Peter Ruppert Oct 26 '20 at 13:37
  • Hi , is there anyway to apply a sort order to newArray/ UNNotificationRequest Array. I get errors of has no member compare. I have managed to find a compare that creates a new array of UNCalendarNotificationTrigger but think when using that the indexing or id is messed up so a delete deletes the wrong notifications. ( I'm only using Hour and Minutes as this is a daily notification note a dated one – markhunte Nov 23 '22 at 11:42