Reputation: 1296
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
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