The lifecycle I am testing is: Start App Online -> Add item -> go offline -> kill the app -> open app -> add item -> go online. If I try adding a new item after going online I get 'NSInternalInconsistencyException', reason: 'attempt to insert row 9 into section 0, but there are only 0 rows in section 0 after the update' Not sure why it believes there are only 0 rows in section 0.
Relevant Code:
let realm = try! Realm()
let owners = try! Realm().objects(OwnerList.self).first?.list
// Notification token defined in viewDidLoad()
self.notificationToken = self.owners?.addNotificationBlock { [unowned self] changes in
switch changes {
case .initial:
self.tableView.reloadData()
case .update(_, let deletions, let insertions, let modifications):
// Query results have changed, so apply them to the UITableView
self.tableView.beginUpdates()
self.tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
self.tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
self.tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .none)
self.tableView.endUpdates()
case .error(let error):
print("Notification Error", error)
break
}
}
// Helper Function when inserting item
func insertItem() throws {
self.realm.beginWrite()
let owner = Owner().generate()
self.owners?.insert(owner, at: (owners?.count)!)
self.tableView.insertRows(at: [[0, (owners?.count)!-1]], with: .automatic)
try self.realm.commitWrite(withoutNotifying: [self.notificationToken!])
}
You shouldn't insert rows until write transaction is committed because write transactions could potentially fail.
Try to move
self.tableView.insertRows(at: [[0, (owners?.count)!-1]], with: .automatic)
after realm.commitWrite(..).
Also make sure your tableview datasource methods return correct values.
Figured it out, make sure you set your tableview delegate and datasource in viewDidLoad() not viewWillAppear()
Related
I am trying to rearrange items in list through firebase child moved in firebase child listener. But the item to be moved replaces the item at the new position. The sorting is however than properly when I scroll up and down
query.observe(.childMoved) { dataSnapshot in
if let value = dataSnapshot.value as? [String: Any],
let index = self.friends.index(where: {$0.fuid == dataSnapshot.key}) {
let indexPath = IndexPath(item: index, section: 0)
let movedPost = friendsModel(snapshot: dataSnapshot)
self.friends.remove(at: index)
self.friends.insert(movedPost, at: 0)
self.tableView.reloadRows(at: [IndexPath(item: 0, section: 0)], with: .none)
self.tableView.reloadRows(at: [indexPath], with: .none)
}
}
Your .childMoved listener gets called with the DataSnapshot that was moved and the previousSiblingKey, which is the key of the item after which you need to reinsert the snapshot in your friends list. Since your code doesn't use previousSiblingKey, it doesn't have enough information to process the move correctly.
See the Firebase documentation: https://firebase.google.com/docs/reference/swift/firebasedatabase/api/reference/Classes/DatabaseQuery#observe_:andprevioussiblingkeywith:withcancel:
I'm having an issue with Realm subscriptions observers. The state is not updating after receiving data from the server. I'm using this code below:
let realm = try! Realm(configuration: try configuration(url: realmURL))
let results: Results<Object> = realm!.objects(Object.self)
let subscription = results.subscribe(named: "objects")
subscription.observe(\.state, options: .initial) { state in
print("Sync State Objects: \(state)")}
The only state I get is ".creating" and after that nothing more is updated. I would like to get ".completed" to be able to track progress of the subscription getting data.
Important to mention that I already tried to remove the options but in this case even ".creating" is not triggered.
Thanks
I will answer with some partial code as it will provide some directon to get this working. Assume we have a PersonClass, a tableView and a tableView dataSource called personResults. This was typed here so don't just copy paste as I am sure there are a couple of build errors.
In our viewController...
class TestViewController: UIViewController {
let realm: Realm
let personResults: Results<Person>
var notificationToken: NotificationToken?
var subscriptionToken: NotificationToken?
var subscription: SyncSubscription<Project>!
then later when we want to start sync'ing our personResults
subscription = personResults.subscribe()
subscriptionToken = subscription.observe(\.state, options: .initial) { state in
if state == .complete {
print("Subscription Complete")
} else {
print("Subscription State: \(state)")
}
}
notificationToken = personResults.observe { [weak self] (changes) in
guard let tableView = self?.tableView else { return }
switch changes {
case .initial:
// Results are now populated and can be accessed without blocking the UI
print("observe: initial load complete")
tableView.reloadData()
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()
case .error(let error):
// An error occurred while opening the Realm file on the background worker thread
fatalError("\(error)")
}
}
I am using realm swift for my messaging app. My base view for messaging is a custom UICollectionView and my i use realm swift for data storage. Unfortunately i couldn't find any official example from realm for updating collection view with realm notification. So i implemented like this when i get messages from realm getting notificationToken by adding an observer for that realm list so when update comes from that notification i perform batch updates and delete, insert and modify indexes that realm told me to do in this notification. my app works well in testing environment but in production i got some crashes that reported my by fabric that told me app crashing and it is caused by this batch updates. their messages are mostly like
invalid update: invalid number of sections. The number of sections contained in the collection view after the update (1) must be equal to the number of sections contained in the collection view before the update (1), plus or minus the number of sections inserted or deleted (0 inserted, 1 deleted).
or
invalid number of items in section after update and so on .
I am so confused now. any ideas?
My code for doing this updates
notificationToken = dbmsgs?.observe { [weak self] (changes: RealmCollectionChange) in
switch changes{
case .initial:
print("intial")
self?.collectionView.reloadData()
case .update(_,let deletions,let insertions,let modifications):
self?.collectionView.performBatchUpdates({
if deletions.count > 0{
print("deletions count\(deletions.count)")
if (self?.collectionView.numberOfItems(inSection: 0) ?? 0) - deletions.count > 0 {
self?.collectionView.deleteItems(at: deletions.map { IndexPath(row: $0, section: 0) })
}
else{
self?.collectionView.deleteSections(IndexSet(integer: 0))
}
}
if insertions.count > 0{
print("insertaions count\(insertions.count)")
for index in insertions{
guard let lastdbmsg = self?.dbmsgs?[index] else{
return
}
let sectionIndex = 0
let itemIndex = ( self?.dbmsgs != nil) ? (self?.dbmsgs?.count)! - 1 : 0
if itemIndex == 0{
self?.collectionView.insertSections([0])
}
self?.collectionView.insertItems(at: [IndexPath(item: itemIndex, section: sectionIndex)])
if itemIndex != 0{
self?.collectionView.reloadItems(at: [IndexPath(row: itemIndex-1, section: 0)])
}
}
}
if modifications.count > 0{
self?.collectionView.reloadItems(at: modifications.map({ IndexPath(row: $0, section: 0)}))
}
},completion: { (_) in
})
case .error(let error):
// An error occurred while opening the Realm file on the background worker thread
fatalError("\(error)")
}
}
I currently have a Realm database with three objects:
class Language: Object {
let lists = List<List>()
}
class List: Object {
let words = List<Word>()
}
class Word: Object {
dynamic var name : String = ""
}
So the object relation is: Language -> List -> Word
I display these objects in a UITableView, in a LanguageTableViewController, ListTableViewController and a WordTableViewController, respectively.
To update the TableView when changes occur, I use Realm Notifications in the LanguageTableViewController:
languageToken = languages.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in
self?.updateLanguages(changes: changes)
}
With the current method to update the TableView (method is based on tutorials from the Realm site):
func updateLanguages<T>(changes: RealmCollectionChange<T>) {
switch changes {
case .initial:
self.tableView.reloadData()
case .update(_, let deletions, let insertions, let updates):
self.tableView.beginUpdates()
self.tableView.insertRows(at: insertions.map {IndexPath(row: $0, section: 0)}, with: .automatic)
self.tableView.reloadRows(at: updates.map {IndexPath(row: $0, section: 0)}, with: .automatic)
self.tableView.deleteRows(at: deletions.map {IndexPath(row: $0, section: 0)}, with: .automatic)
self.tableView.endUpdates()
default: break
}
}
Now to my issue: This method works when new "Language" objects are added or updated. However, when a new List or Word object is created, this method is called too - with an insertion update. This leads to a UITableView Exception, because no Language objects were created, but the updateLanguages method wants to insert new cells.
My question is: How can I only monitor changes to the count/number of objects? Or is there a better approach to this issue?
Realm Collection Notifications works fine while mapping with UITableView rows using 'map'. How do i achieve the same by mapping it to UITableView sections.
For rows I follow the below code:
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.insertRowsAtIndexPaths(insertions.map { NSIndexPath(forRow: $0, inSection: 0) },
withRowAnimation: .Automatic)
tableView.deleteRowsAtIndexPaths(deletions.map { NSIndexPath(forRow: $0, inSection: 0) },
withRowAnimation: .Automatic)
tableView.reloadRowsAtIndexPaths(modifications.map { NSIndexPath(forRow: $0, inSection: 0) },
withRowAnimation: .Automatic)
tableView.endUpdates()
break
case .Error(let error):
// An error occurred while opening the Realm file on the background worker thread
fatalError("\(error)")
break
}
}
For sections, I work with:
tableview.beginUpdates()
for insertIndex in insertions {
tableview.insertSections(NSIndexSet(index: insertIndex), withRowAnimation: .Automatic)
}
for deleteIndex in deletions {
tableview.deleteSections(NSIndexSet(index: deleteIndex), withRowAnimation: .Automatic)
}
for reloadIndex in modifications {
tableview.reloadSections(NSIndexSet(index: reloadIndex), withRowAnimation: .Automatic)
}
tableview.endUpdates()
And this works.
But I want to know about 'map' and how to use it to map sections.
tableView.insertSections(insertions.map { NSIndexSet(index: $0) }, withRowAnimation: .Automatic)
And also,
tableview.insertSections(insertions.map({ (index) -> NSIndexSet in
NSIndexSet(index: index)
}), withRowAnimation: .Automatic)
But, both gives me the same error
'map' produces '[T]', not the expected contextual result type 'NSIndexSet'
map returns a new collection by replacing each of the original collection elements with a mapped version of that same element. In other words:
insertions.map { ...}
returns an array, while tableView.insertSections expects a single NSIndexSet argument.
The closest you're going to get is:
for indexSet in insertions.map { NSIndexSet(index: $0) } {
tableView.insertSections(indexSet, ...)
}
Alternatively, you can create a NSIndexSet that's a conjunction of the individual elements using reduce, something like:
tableView.insertSections(insertions.reduce(NSMutableIndexSet()) {
$0.addIndex($1)
return $0
}, withRowAnimation: .Automatic)
But that really seems to be obscuring the code rather than clarifying it.