Delete objects from List in Realm - Swift - ios

I have a collection view where you can select multiple cells to delete. This means that if multiple cells are deleted, then multiple objects should also be deleted in Realm - 1 object per cell.
I have a function which takes an array of Int's which will be populated from the selected indexPaths of the collection view.
The problem is that I'm not sure how to do both
1) Delete the objects in Realm and
2) Have the List up to date without the deleted objects
My code is:
I get the index paths like so:
let indexPaths = collectionView.indexPathsForSelectedItems
This is my function to take in the indexPaths, update the List, and delete the objects in Realm. It is currently not working because the objects are not being deleted. I noticed that removeAll does not delete anything.
func removeVideos(at indexes: [Int]) {
let newVideos = List<Video>()
for (index, video) in favorite!.videos.enumerated() {
if !indexes.contains(index) {
newVideos.append(video)
}
}
let realm = try! Realm()
try! realm.write {
favorite!.videos.removeAll()
newVideos.forEach { newVideo in
favorite!.videos.append(newVideo)
}
}
}
I call that function like so:
removeVideos(at: indexPaths.map { $0.item })
Any thoughts?

List.removeAll() doesn't delete the objects from Realm. It just removes them out of that List object, deleting their relationship to their parent object (in this case, the favorite object). Deleting objects along with their parent List object is a feature called 'cascading deletes' and it's still being discussed on the Realm GitHub.
If you actually want to delete them, then simply call realm.delete(favorite!.videos). This will delete them from Realm and automatically clear out the List property.
You might need to be careful with your implementation there though. Once an Object has been deleted from Realm, any existing references to it cannot be re-added back into Realm. It may just be appropriate to just delete the newVideo objects themselves instead of cleaning out the whole List.
func removeVideos(at indexes: [Int]) {
let newVideos = [Video]()
for (index, video) in favorite!.videos.enumerated() {
if !indexes.contains(index) {
newVideos.append(video)
}
}
let realm = try! Realm()
try! realm.write {
realm.delete(newVideos)
}
}
As long as you've set a Realm notification block on your collection view, this should be all you need to do for them to be removed from your UI.

Related

Compare the two lists and change the value of the properties of the objects in one of the lists

I have the two lists of AddItem objects. AddItem is a custom object made of data downloaded from the web. The second one list is also AddItem, but this one is saved in the database. I use it to create another list, but in this case user of the app decides which object are important for him.
This want I want to achieve is to mark every single object of the first AddItem list (not saved in the database, create during the start of the view), to show in the TableView which one is saved in the database, so I already use him in another view. You know what I mean. There is a TableView list and if I am interested in a cell I select it and add it to the database.
I hope I have described it clearly. If not, ask for questions.
The first AddItem list (not saved in the database):
func setAddItemList(stations: [Station], sensors: [Sensor]) {
var addItems = [AddItem]()
var sensorItems = [SensorItem]()
let falseValue = RealmOptional<Bool>(false)
addList = try persistenceService.fetchAddItems().toArray(ofType: AddItem.self) //The second list with saved data in the database
let addItem = stations.map { station in
AddItem(
id: station.id,
stationId: station.id,
cityName: station.city?.name ?? "",
addressStreet: station.addressStreet!,
added: falseValue,
sensor: [])
}
addItems.append(contentsOf: addItem)
As you can see, it's create by already downloaded data. I decided to add the property - added, which is the bool property and mark it as true if selected the right cell. Unfortunately I don't know how to do this when creating a list of AddItem objects. The saved array is almost the same. There is only more data, but ids, names, addresses and so on are same, so there are loads of the same data for comparison
I made the solution myself:
addItem.forEach { item in
guard let index = addList2.firstIndex(where: { $0.id == item.id})
else {
print("Failed to find the SavedAddItem for the AddItem \(item.id)")
return
}
addItems[index + 1].added = trueValue
}

How to create initial Realm objects that get added upon installation of app

Say I am creating an object that takes two strings and acts like a dictionary.
class WordInDictionary: Object {
#objc dynamic var word: String = ""
#objc dynamic var meaning: String = ""
What should I do if I wanted to have some initial objects that get added to the database just once upon installation/update of the app?
Also, is there a way to make it so that just those initial objects can't be deleted?
"What should I do if I wanted to have some initial objects that get added to the database just once upon installation/update of the app?"
One option would be to have some code near the realm initialisation that checks if there are any WordInDictionary objects already in the realm - if not then add the required default objects.
E.g.
let realm = try! Realm()
if realm.objects(WordInDictionary.self).isEmpty
{
// Add required words here
}
"Also, is there a way to make it so that just those initial objects can't be deleted?"
I don't know of a way to make realm objects read-only. You'd have to implement this in code in some way, e.g. have a isDeletable boolean member which is true for every user-created object and false for your default members, then only delete those from realm.
E.g. for your deletion code:
func deleteWords(wordsToDelete: Results<WordInDictionary>)
{
try! realm.write
{
realm.delete(wordsToDelete.filter("isDeletable = true")
}
}

Firebase query observing reshowing data

I have a firebase query that observes data from a posts child.
func fetchPosts () {
let query = ref.queryOrdered(byChild: "timestamp").queryLimited(toFirst: 10)
query.observe(.value) { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
if let value = child.value as? NSDictionary {
let post = Post()
let poster = value["poster"] as? String ?? "Name not found"
let post_content = value["post"] as? String ?? "Content not found"
let post_reveals = value["Reveals"] as? String ?? "Reveals not found"
post.post_words = post_content
post.poster = poster
post.Reveals = post_reveals
self.postList.append(post)
DispatchQueue.main.async { self.tableView.reloadData() }
//make this for when child is added but so that it also shows psots already there something like query.observre event type of
}
}
However, when a user posts something, it creates a more than one cell with the data. For instance, if I post "hello", a two new cards show up with the hello on it. However, when I exit the view and recall the fetch posts function, it shows the correct amount of cells. Also, when I delete a post from the database, it adds a new cell as well and creates two copies of it until I reload the view, then it shows the correct data from the database.
I suspect this has something to do with the observe(.value), as it might be getting the posts from the database and each time the database changes it creates a new array. Thus, when I add a new post, it is adding an array for the fact that the post was added and that it now exists in the database, and when I refresh the view it just collects the data directly from the database.
Also, sometimes the correct amount of cells show and other times there's multiple instances of random posts, regardless of whether I have just added them or not.
How can I change my query so that it initially loads all the posts from the database, and when some post is added it only creates one new cell instead of two?
Edit: The logic seeming to occur is that when the function loads, it gets all the posts as it calls the fetchPosts(). Then, when something is added to the database, it calls the fetchPosts() again and adds the new data to the array while getting all the old data. yet again.
One thing I always do when appending snapshots into an array with Firebase is check if it exists first. In your case I would add
if !self.postList.contains(post) {
self.postList.append...
however, to make this work, you have to make an equatable protocol for what I'm guessing is a Post class like so:
extension Post: Equatable { }
func ==(lhs: Post, rhs: Post) -> Bool {
return lhs.uid == rhs.uid
}
You are right in thinking that the .value event type will return the entire array each time there is a change. What you really need is the query.observe(.childAdded) listener. That will fetch individual posts objects rather than the entire array. Call this in your viewDidAppear method.
You may also want to implement the query.observe(.childRemoved) listener as well to detect when posts are removed.
Another way would be to call observeSingleEvent(.value) on the initial load then add a listener query.queryLimited(toLast: 1).observe(.childAdded) to listen for the latest post.

Use Realm with Collection View Data Source Best Practise

I'll make it short as possible.
I have an API request that I fetch data from (i.e. Parse).
When I'm getting the results I'm writing it to Realm and then adding them to a UICollectionView's data source.
There are requests that take a bit more time, which run asynchronous. I'm getting the needed results after the data source and collection view was already reloaded.
I'm writing the needed update from the results to my Realm database.
I have read that it's possible to use Realm's Results. But I honestly didn't understood it. I guess there is a dynamic and safe way working with collection views and Realm. Here is my approach for now.
This is how I populate the collection view's data source at the moment:
Declaration
var dataSource = [Realm_item]()
where Realm_item is a Realm Object type.
Looping and Writing
override func viewDidLoad() {
super.viewDidLoad()
for nowResult in FetchedResultsFromAPI
{
let item = Realm_item()
item.item_Title = nowResult["Title"] as! String
item.item_Price = nowResult["Price"] as! String
// Example - Will write it later after the collectionView Done - Async request
GetFileFromImageAndThanWriteRealm(x.image)
// Example - Will write it later after the collectionView Done - Async request
dataSource.append(item)
}
//After finish running over the results *Before writing the image data*
try! self.realm.write {
self.realm.add(self.dataSource)
}
myCollectionView.reloadData()
}
After I write the image to Realm to an already created "object". Will the same Realm Object (with the same primary key) automatically update over in the data source?
What is the right way to update the object from the data source after I wrote the update to same object from the Realm DB?
Update
Model class
class Realm_item: Object {
dynamic var item_ID : String!
dynamic var item_Title : String!
dynamic var item_Price : String!
dynamic var imgPath : String?
override class func primaryKey() -> String {
return "item_ID"
}
}
First I'm checking whether the "object id" exists in the Realm. If it does, I fetch the object from Realm and append it to the data source. If it doesn't exist, I create a new Realm object, write it and than appending it.
Fetching the data from Parse
This happens in the viewDidLoad method and prepares the data source:
var query = PFQuery(className:"Realm_item")
query.limit = 100
query.findObjectsInBackgroundWithBlock { (respond, error) -> Void in
if error == nil
{
for x in respond!
{
if let FetchedItem = self.realm.objectForPrimaryKey(Realm_item.self, key: x.objectId!)
{
self.dataSource.append(FetchedItem)
}
else
{
let item = Realm_item()
item.item_ID = x.objectId
item.item_Title = x["Title"] as! String
item.item_Price = x["Price"] as! String
let file = x["Images"] as! PFFile
RealmHelper().getAndSaveImageFromPFFile(file, named: x.objectId!)
self.dataSource.append(item)
}
}
try! self.realm.write {
self.realm.add(self.dataSource)
}
self.myCollectionView.reloadData()
print(respond?.count)
}
}
Thank you!
You seem to have a few questions and problems here, so I'll do my best.
I suggest you use the Results type as your data source, something like:
var dataSource: Results<Realm_item>?
Then, in your viewDidLoad():
dataSource = realm.objects(Realm_item).
Be sure to use the relevant error checking before using dataSource. We use an optional Results<Realm_item> because the Realm object you're using it from needs to be initialised first. I.e., you'll get something like "Instance member * cannot be used on type *" if you try declaring the results like let dataSource = realm.objects(Realm_item).
The Realm documentation (a very well-written and useful reference to have when you're using Realm as beginner like myself), has this to say about Results...
Results are live, auto-updating views into the underlying data, which means results never have to be re-fetched. Modifying objects that affect the query will be reflected in the results immediately.
Your mileage may vary depending on how you have everything set up. You could try posting your Realm models and Parse-related code for review and comment.
Your last question:
What is the right way to update the "object" from the Data Source after i wrote the update to same object from the Realm DB?
I gather you're asking the best way to update your UI (CollectionView) when the underlying data has been updated? If so...
You can subscribe to Realm notifications to know when Realm data is updated, indicating when your app’s UI should be refreshed for example, without having to re-fetch your Results.

Realm: rearranging list of objects

Please, tell me, how to rearrange items of Realm's list of objects by index? I.e. I'm looking something like
let movingElement = array[oldIndex]
array.removeAtIndex(oldIndex)
array.insert(movingElement, atIndex: newIndex)
if it was with a casual Swift array of something.
But for List in Realm I can not do the same thing:
let realm = try! Realm()
var all = try! Realm().objects(element)
realm.write {
all.removeAtIndex() // all of type
Another option is to
let realm = try! Realm()
let element = try! Realm().objects(Element)[oldIndex]
realm.write{
realm.delete(element)
realm.add(...) // How to set index to place new object at?
}
But how to insert element in proper place? May be there is a proper method how to move elements of realm of the same type (class) by index?
Thanks in advance!
You cannot do it with query results, as they are unordered (or in a specific order if you sort them). But if you put them into Realm List (which you can store as member in a Realm object), then you can use both move and swap methods to reorder elements.
Here is the API docs for the the List type: https://realm.io/docs/swift/latest/api/Classes/List.html

Resources