fisher
fisher

Reputation: 1296

How do I work with realm notification if result is updated after notification is initialized?

I use a UITableView with Realm results as it's data source. The results are registered with a realm notification and is updated very well when changes occur. However, the same table view has a search bar that filters the result based on the users search query. This also works fine, but if the notification listener recognizes an update, the section and row count does not match. What is the correct way to do this? Is it possible to update the NotificationToken?

This is the code that initializes the notification token:

let realm = try! Realm()

results = realm.objects(Person.self).filter("active = '1'").sorted(byProperty: "name", ascending: true)

// Observe Results Notifications
notificationToken = results?.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in

    guard let tableView = self?.tableView else { return }
    switch changes {
    case .initial:
        // Results are now populated and can be accessed without blocking the UI
        tableView.reloadData()
        break
    case .update(_, let deletions, let insertions, let modifications):

        // Query results have changed, so apply them to the UITableView
        tableView.beginUpdates()

        tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic)

        tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
        tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic)
        tableView.endUpdates()
        break
    case .error(let error):
        // An error occurred while opening the Realm file on the background worker thread
        print("Error: \(error)")
        break
    }
}

This code updates the results based on user search input:

func updateSearchResults(for searchController: UISearchController) {

    let searchText = searchController.searchBar.text!

    if searchText.isEmpty == false {

        results = results?.realm?.objects(Person.self).filter("active = '1'")

        results = results?.filter("name BEGINSWITH[c] %@ OR lastName CONTAINS[c] %@", searchText, searchText)

        results = results?.sorted(byProperty: "name", ascending: true)

        self.tableView.reloadData()

    }
    else {

        results = results?.realm?.objects(Person.self).filter("active = '1'").sorted(byProperty: "name", ascending: true)

        self.tableView.reloadData()
    }
}

If a search qwuery has been performed and is preceded with an update to the realm database, an NSRange exception occurs.

Upvotes: 2

Views: 4013

Answers (1)

kishikawa katsumi
kishikawa katsumi

Reputation: 10573

You overwrite results in updateSearchResults(). Notification and notificationToken is tied to specific Results object and query. So if you overwrite results by another query, you should stop the previous notification, then re-add notification to the new results object.

So updateSearchResults() method should be like the following:

func updateSearchResults(for searchController: UISearchController) {
    let searchText = searchController.searchBar.text!
    if searchText.isEmpty == false {
        results = results?.realm?.objects(Person.self).filter("active = '1'")
        results = results?.filter("name BEGINSWITH[c] %@ OR lastName CONTAINS[c] %@", searchText, searchText)
        results = results?.sorted(byProperty: "name", ascending: true)
    }
    else {
        results = results?.realm?.objects(Person.self).filter("active = '1'").sorted(byProperty: "name", ascending: true)
    }

    notificationToken.stop()
    notificationToken = results?.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in
        guard let tableView = self?.tableView else { return }
        switch changes {
        case .initial:
            tableView.reloadData()
            break
        case .update(_, let deletions, let insertions, let modifications):
            tableView.beginUpdates()

            tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic)

            tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
            tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic)
            tableView.endUpdates()
            break
        case .error(let error):
            print("Error: \(error)")
            break
        }
    }
}

Upvotes: 9

Related Questions