2

I've seen a dozen or so tutorials on how to use Combine and receive a Notification of a task being completed. It seems they all show linear code - the publisher and receiver all in the same place, one row after another.

Publishing a notification is as easy as the code below:

// background download task complete - notify the appropriate views
DispatchQueue.main.async {
    NotificationCenter.default.post(name: .dataDownloadComplete, object: self, userInfo: self.dataCounts)
}

extension Notification.Name {
    static let dataDownloadComplete = Notification.Name("dataDownloadComplete")
}

SwiftUI has the onReceive() modifier, but I can't find any way to connect the above to a "listener" of the posted notification.

How does a View receive this Notification

David
  • 3,285
  • 1
  • 37
  • 54
  • Does this answer your question? [How to set addObserver in SwiftUI?](https://stackoverflow.com/questions/58818046/how-to-set-addobserver-in-swiftui) – pawello2222 Sep 22 '20 at 16:43
  • @pawello2222 Thanks for the link. That was one page I saw, that got me pointed in the right direction. – David Sep 22 '20 at 18:47

1 Answers1

4

FYI, after several days of reading and putting together the confusing-to-me Combine tutorials, I discovered these two methods of receiving the notification. For ease, they are included in the same View. (Some not-related details have been omitted.)

In my case, a fetch (performed on a background thread) is batch loading info into Core Data for several entities. The View was not being updated after the fetch completed.

// in the background fetch download class
    ...
    var dataCounts: [DataSources.Source : Int] = [:]
    ...

    // as each source of data has been imported
    self.dataCounts[source] = numberArray.count
    ...


// in a view
import Combine

struct FilteredPurchasesView: View {
 
    private var downloadCompletePublisher: AnyPublisher<Notification, Never> {
        NotificationCenter.default
            .publisher(for: .dataDownloadComplete)
            .eraseToAnyPublisher()
    }
    
    private var publisher = NotificationCenter.default
        .publisher(for: .dataDownloadComplete)
        .map { notification in
            return notification.userInfo as! [DataSources.Source : Int]
        }
       .receive(on: RunLoop.main)

    var body: some View {
        List {
            ForEach(numbers.indices, id: \.self) { i in
                NavigationLink(destination: NumberDetailView(number: numbers[i])) {
                    NumberRowView(number: numbers[i])
                }
                .id(i)
            }
        }
        .add(SearchBar(searchText: $numberState.searchText))
        .onReceive(downloadCompletePublisher) { notification in
            print("dataDownload complete (FilteredPurchasesView 1)")
            if let info = notification.userInfo as? [DataSources.Source:Int], let purchaseCount = info[DataSources.Source.purchased] {
                if purchaseCount > 0 {
                    // now the view can be updated/redrawn
                } else {
                    print("purchase update count = 0")
                }
            }
        }
        .onReceive(publisher) { notification in
            print("dataDownload complete (FilteredPurchasesView 2)")
        }
    }
}

Some notes about this:

  1. During the first couple of attempts, the FilteredPurchasesView had not yet been initialized. This meant there was no Subscriber to listen for the posted notification.
  2. Both of the Publisher vars are available. As of this writing, I cannot explain why or how they work.
  3. Both onReceive() modifiers contain the notification.

Comments, ideas and feedback welcome.

David
  • 3,285
  • 1
  • 37
  • 54