Issue with Realm Notifications and UITableView - ios

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?

Related

Updating old swift programming project, what would cause the error: "Value of type '[ToDoItem]' has no member 'removeAtIndex'"

New to swift programming and have run into the error:
"Value of type '[ToDoItem]' has no member 'removeAtIndex'"
for this code block:
func toDoItemDeleted(toDoItem: ToDoItem) {
let index = (toDoItems as NSArray).indexOfObject(toDoItem)
if index == NSNotFound { return }
// could removeAtIndex in the loop but keep it here for when indexOfObject works
toDoItems.removeAtIndex(index)
// use the UITableView to animate the removal of this row
tableView.beginUpdates()
let indexPathForRow = NSIndexPath(forRow: index, inSection: 0)
tableView.deleteRowsAtIndexPaths([indexPathForRow], withRowAnimation: .Fade)
tableView.endUpdates()
} ```
This is old code that was used from this website: ```https://www.raywenderlich.com/2153-how-to-make-a-gesture-driven-to-do-list-app-like-clear-in-swift-part-1-2
Is the function removeAtIndex deprecated?
Please help. Thank you!
This is Swift 2 code, outdated for many years.
Nowadays you can write it dramatically shorter
func deleteTodoItem(_ toDoItem: ToDoItem) {
guard let index = toDoItems.firstIndex(of: toDoItem) else { return }
toDoItems.remove(at: index)
tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .fade)
}

Firebase child listener on child moved

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:

iOS: Realm subscription state never updated

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)")
}
}

Realm NSInternalInconsistencyException When Starting App Offline

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()

How to check for RLMResults invalidated?

When my user logs out, I clear my realm with realm.deleteAll(). After this, I get a lot of notifications resulting in reads from Results objects, which results in an exception realm::Results::InvalidatedException, "RLMResults has been invalidated". I can't find a way to
check a Results object for invalidation directly;
check a Results' realm object for invalidation; or
get the List the Results is derived from in order to check its invalidation state.
I can't think of anything else to look for. If there's a better way to clear the database that won't result in exceptions all over the place, I'd be happy to hear about that too.
Additional information: the exception is thrown even when calling count on a Results object, not just accessing its objects.
You can check if the first object exist, from Swift Docs:
public var first: T? { return rlmResults.firstObject() as! T? }
Returns the first object in the results, or nil if empty.
From Realm Documentation for Java (could not find the same wording in Swift Docs):
Notice that a RealmResults is never null not even in the case where it contains no objects. You should always use the size() method to check if a RealmResults is empty or not.
Long story short, check if the first object exist of or try to count the elements.
Sources:
Java - Class RealmResults,
Swift - Results Class Reference
EDIT: Here is a code sample, it is taken from Realm example and modified to my needs, they use notification token to detect if the array is empty
class Record: Object {
dynamic var workoutName = ""
dynamic var totalTime = ""
dynamic var date = ""
}
let realm = try! Realm()
let array = try! Realm().objects(Record).sorted("date")
var notificationToken: NotificationToken?
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
// Set results notification block
notificationToken = array.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.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
}
}
}
I also clear the table using deleteAll():
func clearTable() {
try! realm.write {
realm.deleteAll()
}
}
Results now has an invalidated property, as of 1.0.3.
Source: https://github.com/realm/realm-cocoa/blob/v0.103.0/CHANGELOG.md

Resources