I have a custom TableViewController called LibraryTableViewController.
class LibraryTableViewController: UITableViewController {
contains this data for rendering.
var items: [item] = []
Then, I have another class for Firebase that inherits the LibraryTableViewController as a delegate. (I am not sure if this is the right description.)
class FirebaseDB : LibraryTableViewController {
Then, I have a global instance of FirebaseDB as
let database = FirebaseDB()
I have an observer listening to the changes in the database in the Firebase class, and I was updating
self.items
Then, trying to refresh the LibraryTableViewController using
self.tableView.reloadData()
I might be wrong in understanding my current problem. What I think is that the items array that I am updating isn't the same instance as the tableView that I see on the app. It is updating the FirebaseDB instance database's items which isn't being rendered on the app. How do I make sure that the right instance is being updated?
a lot of code missing
but you can check:
thread must be main, maybe you call from background
check is the items is changed
the numbers for you
check is the tableview not nil
check your delegate maybe its also nil
but better give us more code)
Related
This is my code to populate my 'Consumables' array (called in my viewDidLoad):
//---------------------- POPULATE CONSUMABLE ARRAY --------------------------------//
private func populateConsumableArray(){
//let the object populate itself.
self.ref?.child("Consumables").observe(.childAdded, with: { snapshot in
let dataChange = snapshot.value as? [String:AnyObject]
let aRequest = Consumable(aDict: dataChange!)
self.consumableArray.append(aRequest)
self.consumableTable.reloadData()
})
}
The Consumable object class is shown below
public class Consumable{
private var type:String
private var count:String
private var sku:String
init (aDict: [String: AnyObject]){
self.type = aDict["Type"] as! String
self.count = aDict["Count"] as! String
self.sku = aDict["SKU"] as! String
}
The data populates my table view just fine... Below is a picture of the code working...
As you can see by the two images above, the code loads the data just fine... The array is populated just fine as well, not shown because it's not directly related to the problem.
Below is a picture of the database structure:
Now when a new consumable is added via my add function the child added only grabs the first attribute added to the consumable item. (also not included, because I'm sure that is working fine, since it populates the firebase database online, which I will show)
The first attribute being 'Type', I switched the order of how things are added to firebase, added 'Count' first and count ended up being the only attribute grabbed. See the image below for what I mean...
Adding a test consumable:
Now you can see that 'Type' is the only attribute being grabbed and stored in the dataChange dictionary, rather than 'Type', 'Count' and 'Sku'.
This is what my firebase database looks like at the above breakpoint so I know that consumables are being added just fine to firebase, it's just a problem with grabbing them when a new child is added to the 'Consumable' parent:
And then, of course, the failure occurs in my Consumable object init function, since 'Count' and 'Sku' are not in the passed dictionary. See below for the fault:
If I close and reload the application, the table view loads with all the data, even the data I previously added that crashed the app. So my question, why does my populateConsumableArray function not grab all the children of the "Consumables" parent?
For the heck of it, I'll add my addConsumableToFirebase() function below, just in case that is the problem for some reason...
Before I start please post code as text not as pictures, so we can actually copy and paste it if needed... (you mostly did but the issue was indeed in the last pic you posted lol)
Now, looking at the Firebase documentation looks like:
setValue(_:) says:
The effect of the write will be visible immediately and the corresponding events will be triggered. Synchronization of the data to the Firebase Database servers will also be started.
Which is why you're seeing it call immediately with only the Type.
Instead try the provided function for updating multiple value at the same time:
func updateChildValues(_ values: [AnyHashable : Any])
Edit:
Removed a part about probably causing a retain cycle since you don't capture self weakly, but as pointed out in the comments, it doesn't apply here.
I've just starting to using Realm and feel it's very good, fast except one thing: delete an object in Realm is easily cause an exception.
Is there any way I can delete an object in Realm safety?
In my project, I usually have to create, update, delete hundred objects on the background thread. The issue is:
If the app currently display/using one object on the main thread
In the background, I delete that object.
=> On the main thread will cause an exception when using that object's properties.
I know Realm has isInvalid method to check, but I cannot add the check in every assign properties code, it's look not good.
So, as of now, what I do is: instead of actually delete, I have a property call "deleted", and in delete, I only update that value. And on the UI, I will filtered out objects which have deleted = true
I wonder is there any way better to do this?
This is intended functionality. If a background thread deletes a Realm Object, the next time you try and access that object from any thread, an exception will be thrown.
In order to handle this, Realm provides a rich notification system that you can use to automatically receive alerts of when the contents of a Realm database have been changed and to update the UI accordingly.
If you've got a view controller that is displaying the contents of a single Realm Object, you could implement a system to be notified of any changes being made to your Realm database, and then check to make sure your object is still valid:
class MyViewController : UIViewController {
var myModel: Object = nil
var notificationToken: NotificationToken? = nil
init(model: Object) {
self.myModel = model
}
override fun viewDidLoad() {
super.viewDidLoad()
notificationToken = myModel.realm.addNotificationBlock { notification, realm in
guard myModel.invalidated == false else {
// The object has been deleted, so dismiss this view controller
}
}
}
deinit() {
notificationToken?.stop()
}
}
That notification block will be triggered each time a write transaction modified something in that particular Realm file (Even on background threads), which gives you a chance to check to see if your particular Realm Object in that UI hasn't been deleted. If it has, then you can simply dismiss the UI.
Depending on your specific needs, there is also a more fine-grained notification system you can use to specifically track changes to Realm Objects that were part of the results of a query. There's sample code for that in the Collection Notifications of the Realm documentation.
Please let me know if you need additional clarification! :)
I have a UITableViewController, which is a delegate for an NSFetchedResultsController. My NSFetchedResultsControllerDelegate functions are set up as per "Typical Use" in Apple's documentation, with the table view controller as the fetched result controller's delegate property.
I also have some view controllers which are presented on top of the table view where the managed objects can be modified. These modifications are done on the same managed object context. There is only one managed object context constructed in my application, and it is accessed globally. (I have put some print statements in my object context construction just to be sure I am not accidentally re-constructing it elsewhere.)
When I modify one of the managed objects, the delegate function controller:didChangeObject is called, but the NSFetchedResultsChangeType is always .Delete. When I create a managed object, the delegate function does not fire at all.
However, when I manually call call performFetch() and tableView.reloadData(), the cells are restored to the correct state: the removed row comes back, any not-inserted rows are created.
The result is that deleting an object works as expected (the cell is removed), but updates to an object cause its cell to be removed, and object creations do not trigger cell inserts.
I tried to create a simple demo of this behaviour, but when I re-created the situation from a blank application, I don't see this behaviour. So something within my application is causing this strange behaviour, but I can't figure out what. Any ideas?
Extra Info:
The actual construction of the predicate and sort descriptors are done over several different classes, but printing them via print(resultsController.fetchRequest.predicate) and print(resultsController.fetchRequest.sortDescriptors) gives the following:
Optional(readState == "2" OR readState == "1")
Optional([(readState, ascending, compare:), (title, ascending, compare:)])
I have put a print statement in my controller:didChangeObject: method, and I can see that this only gets called with type.rawValue = 2 (i.e. .Delete), and only when I modify objects, not when I create them.
It's an inconsistency with how NSFetchedResultsController handles its NSPredicate.
If the NSFetchedResultsController is constructed with a fetch request which has a predicate which does a comparison between an integer and a string like follows:
let predicate = NSPredicate(format: "integerAttribute == %#", String(1))
this will lead to the predicate string being:
integerAttribute == "1"
When this is the case, initial fetches work fine: calling the function performFetch() on the fetched results controller returns all objects where the integerAttribute is equal to 1 (where integerAttribute is of type Int32).
However, the notifications to NSFetchedResultsControllerDelegate do not work fine. Modifications of managed objects result in the delegate being notified of a NSFetchedResultsChangeType.Delete change. Creations of managed objects do not invoke the delegate at all.
To make all this weirdness go away, fix the predicate format string as follows:
let predicate = NSPredicate(format: "integerAttribute == %d", 1)
I am trying out Realm.io on my Swift project. The insertion and update of objects are pretty straightforward, but here comes a problem: I am not able to catch a new object insertion/update notification.
What I want to achieve is simple, I save a list of objects in Realm. And upon app start/refresh, the app will request a new list of objects from remote server and then perform realm.add(objects, update:true) (I've set id as the object's primary key so that the same objects will not be duplicated), then the UI end of my app should be notified whenever there's a new object, or any existing objects have been updated.
I've tried using realm.addNotificationBlock(_:) but it's called every time with a RLMRealmDidChangeNotification event, even though there is no new object/update.
How do I achieve this?
Edit: code sample
public class DataStorageManager {
var token : NotificationToken?
static let sharedInstance = DataStorageManager ()
public func saveListA(list: [A]?, realm:Realm) {
self.token = realm.addNotificationBlock({ (notification, realm) -> Void in
print("database changed")
})
if list?.count > 0 {
try! realm.write {
realm.add(list!, update:true)
}
}
}
}
You should call addNotificationBlock only once and not everytime you call saveListA. So you could move it to the DataStorageManager's init method.
But you wrote that you want to update your UI whenever the list is updated, so instead of having the token inside your DataStorageManager class you could directly add the NotificationToken as a property to your UIViewController class and call addNotificationBlock in your view controller's viewDidLoad method. Then you can directly update your UI inside the notification block.
EDIT:
If you only want to update your UI when certain data gets updated you cannot use Realm's notification system (which sends a notification everytime any data is changed).
You can do one of the following
Use KVO on your Realm objects. This is described in the Realm Docs
Send your own NSNotification whenever the data is updated that needs a refresh of your UI. So in your case you can send an NSNotification everytime your list gets changed in saveListA. Then you register your view controller as an observer to that notification and update your UI whenever you receive that notification.
As I understand it, you should fetch the desired objects somewhere you want to hold them (like your data manager or view controller), getting Results object and subscribe to this Results change.
Please refer to https://realm.io/docs/swift/latest/#collection-notifications.
It doesn't matter where the object would be changed as long as you hold to that notifications token.
The downside is, currently "modifications" array always include every "touched" element, regardless of if there had been any changes at all. It is considered a bug, but no progress there since August '17.
https://github.com/realm/realm-cocoa/issues/3489
For now, I compare modifications manually after getting the notification.
I am doing a Parse query from an external class. This query give me back the array for populate the tableview, so in the viewDidLoad method I call MyClass.load(array) and the async parse method findObjectInBackground return me the array filled but in the time that the findObject retrive me the objects the tableView is already created and I see an empty tableView, I did a fast google search for the problem and I found that I have to use the self.tableView.reloadData() method, I tried it but I am in an external class and the delegate for the tableView is in the tableViewController, there is any way to refresh the tableView from an external Class?
If you need some code example just ask it, thank you!
EDIT I'm using Swift 2.0
You need to set custom delegate between your external class and ViewControllers.
check for creating custom delegate
Also, you can use NSNotificationCenter for send and post notifications. please check link