iOS Swift: Delete parse array item referenced by index - ios

I have data stored in parse column with following column keys
phone = ["iPhone", "Galaxy S6", "Nexus"]
quantity = [20, 30, 20]
boolV = [[true, false], [true, false], [false,true]]
I am presenting this date in tableView format to the user and want to give him the option to delete these items.
At present I am following this:
Inside tableView commitEditingStyle function
// find the index value of user selected cell
var indexValue = find(phone, "iPhone") // 0
// Using this, I can delete the array elements from phone like this
quantity.removeAtIndex(indexValue!)
boolV.removeAtIndex(indexValue!)
To delete from parse I using the following:
object.removeObject("iPhone", forKey: "phone") // This works fine
object.removeObject("self.quantity[indexValue]", forKey: "quantity") // But this deletes both "20" quantity from the table
object.removeObject("self.boolV[indexValue]", forKey: "boolV") // again it deleted both "[true, false]" values form the array.
I am sure I am missing something here. I just want to delete array item pointed by the indexValue. How can I do that in Parse? Any help would be appreciated.
Thanks.

This really seems like you should use a different data model with 2 classes and a relationship rather than 1 class and a set of array attributes. Then, to do the deletion you remove an item from the relationship and delete it from the data store.
The way you have it at the moment you can't delete individual items from the array, you need to take the array, edit it and then store the full new updated array.

you should delete in the background like this: objects is your tableView connected array
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
let objectToDelete = objects?[indexPath.row] as! PFObject
objectToDelete.deleteInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
// Force a reload of the table - fetching fresh data from Parse platform
self.loadObjects()
} else {
// There was a problem, check error.description
}
}
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}

Related

Swift 4: Pagination using data from an array to make API call as user scrolls

Background
In my app, I store a bunch of object IDs. I use these IDs to make batch API calls. The API limits each call to 10 ID numbers. This data is rendered on a UITableView. The user can add and delete objects, which adds or removes the object ID from the database.
I’m using a Firestore database to store the object IDs on my end.
Current Implementation
Here’s what I’ve implemented so far, but it crashes the app when add & deleting objects. I haven’t been able to work out how to properly handle these cases & whether this is the right pattern to do something like this.
Get object IDs to be used for making API calls
var objectIds: [String] = []
var chunkedObjectIds: [[String]] = []
var objects: [Array] = []
var offset: Int = 0
override func viewDidLoad() {
super.viewDidload()
getObjectIds()
}
func getObjectIds() {
// get objects IDs and store then in objectIds from the Firestore database
// setup the .addSnapshotLister so the query is triggered whenever there is a change in the data on Firestore for the collection
return chunkedObjectIds
// when finished, get the first 10 objects from the 3rd party API
fetchObjects()
}
Take object Ids array, split into array of arrays (lots of 10) & Make the API call for the first 10
func fetchObjects() {
// split objectIds array in array of arrays, in lots of 10
// chunkedObjectIds is set here
// request the objects for the first 10 ID numbers
Alamofire.request(… parameter with first 10 object ids …) (objects) in {
// save objects
// increment the offset
offset += 1
}
}
Render the data on the UITableView cells
Use the following method to load more data from the 3rd party API:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let lastRow = objects.count
var parameters = [String: Any]()
if indexPath.row == lastRow {
if !(offset == self.chunkedObjectIds.count) {
// process the next batch from the array
parameters["id-numbers"] = self.chunkedObjectIds[offset].map{String($0)}.joined(separator: ",")
Alamofire.request(… paramaters: parameters) { (objects) in
for item in 0..<objects.count {
let indexPath = IndexPath(row: item + self.objects.count, section: 0)
self.paths.append(indexPath)
}
self.objects.append(contentsOf: objects)
self.tableView.beginUpdates()
self.tableView.insertRows(at: self.paths, with: .automatic)
self.tableView.endUpdates()
self.paths.removeAll()
self.offset += 1
}
}
}
}
Adding or deleting objects:
The object ID is added or deleted from the Firestore database
The objectIds, chunkedObjectIds, offset and objects are cleared
The listener triggers a read of the data and the process repeats
The Issue & Question
This works well to load initial data. But duplication occurs when adding (and sometimes crashing). When deleting the app will crash because of out of range exceptions.
Is this the correct pattern to use in the first place? If so, what am I missing to handle cases after the first load, specifically the addition and deletion of new object IDs.
Edit
I have changed the implementation based on feedback in the comments. So now, the process is like this:
Setup listener to get data from Firestore
Loop through the object ids from Firestore and while the counter is < 10 or we reach object.count - Now I save the next offset and the next time it triggers this method, I initiate a loop from the next offset with the same while conditions
Fetch the objects from the 3rd party API
I kept using willDisplay cell method to trigger more data to load - it seemed to work more reliably than scrollDidEnd method.
So now the app doesn't crash anymore. There are some issues with the firestore listener, but I'll post that as a separate question.

Retrieve child by auto id value in firebase (TableView)

I have a UI table view and when I swipe the cell I delete the cell, but not the value in firebase. I have searched everywhere but cannot find it. How can I delete the array I swiped on from the cell in my firebase. Below is how I set up my firebase database. The array I'm trying to delete is the corresponding one in the cell e.g. it may be "-L4BMZBIcYMp_f2LDMbp" etc.
Also the code I have to delete the cell and where I would find the array corresponding to the cell
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
let fliper = self.flipList[indexPath.row]
if let itemID = fliper.item{
FIRDatabase.database().reference().child("Flip").child("..I need to find out how to get child auto id").removeValue(completionBlock: { (error, ref) in
if error != nil {
print("Failed! to delete message")
return
}
//one way of updating the table
self.flipList.remove(at: indexPath.row)
self.tableViewFlips.deleteRows(at: [indexPath], with: .automatic)
})
}`
The "key" here is to ensure that when the data is initially loaded from Firebase you keep the node key intact. So for example. say you have a messaging app that has posts. Here's a class that would store the data from Firebase
struct PostStruct {
post_key = ""
post_text = ""
posted_by_uid = ""
}
and then those structs would be used to populate your dataSource array.
Given the above structure:
When your dataSource is initially populated from Firebase, as you read in each post node, create a post struct, populate it with data from Firebase and store it in a dataSource, typically an array.
When you swipe to delete there are about 100 options for handling the actual delete process but here are a couple:
1) Your tableView is backed by an array. If you swipe to delete row 2 (for example), inspect the object (struct) to be deleted, capture the post_key from the stuct, then delete the node from Firebase (since you know the node key) and then query the array for that object key, delete the row in the array and reload your tableView/update the UI.
2) When you swipe to delete a row, inspect the object and obtain the post_key. Then remove the object from Firebase. That would trigger a .childRemoved event (assuming you've attached the corresponding listener). When your app receives that event, determine which object it is (via key or other means) and remove it from the array and reload the tableView.
3) If you swipe to delete row 2, get the struct from row 2 in your dataSource array, read the key from that struct, remove row 2, remove the object from Firebase and reload the tableView.
There are many, many other way to tackle this; your dataSource array could store the actual snapshot data from firebase or it could store a series of key: value pairs with the key being the node key and the value being the data within that node (as a stuct, class, string etc).
Edit:
Some additional info to clarify the above.
Supposed we are using the structure above and firebase has matching nodes like this
root
posts
post_key_0 //created via childByAutoId
post_text: "some post text"
posted_by_uid: "uid_0"
post_key_1 //created via childByAutoId
post_text: "another post"
posted_by_uid: "uid_1"
then to read the posts and populate a dataSource array:
struct PostStruct {
post_key = ""
post_text = ""
posted_by_uid = ""
}
var dataSourceArray = [PostStruct]()
func button0() {
let itemsRef = self.ref.child("items")
itemsRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let dict = snap.value as! [String: Any]
let postKey = snap.key
let postText = dict["post_text"] as! String
let postedByUid = dict["posted_by_uid"] as! String
let post = PostStruct(post_key: postKey, post_text: postText, posted_by_uid: ppostedByUid)
self.dataSourceArray.append(post)
}
self.someTableView.reloadData()
})
}
from there it's pretty straightforward.
Assuming we are using option 3), when the user swipes to delete row 2 for example you get that post from the array, row 2
let post = self.dataSourceArray[2]
let postKey = post.post_key
now you know the firebase key so it can then be removed
let thisPostRef = fbRef.child("posts").child(postKey)
thisPostRef.remove()
and finally remove it from the array and refresh the tableView
self.dataSourceArray.remove(at: 2)
self.someTableView.reloadData()
The code would be different if you went with one of the other options but the concepts are similar and can be applied to each.

managedContext.insertObject extra argument in call `atIndex` in Swift?

So here is my code: (I want to re-order the table and update Core Data):
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
let entity = NSEntityDescription.entityForName(entity, inManagedObjectContext: managedContext)
let entityObject = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedContext)
entityObject.setValue(content, forKey: key)
self.managedContext.insertObject(entityObject, atIndex: 0) //ERROR HERE
do {
try managedContext.save()
} catch let error as NSError {
}
}
I've seen similar code here but why mine isn't working? Thanks!
The link you provided is not doing the same thing you did here. What he did there was removing and inserting object in to a array of Playlist.
If you really want to re-order the table and update Core Data accordingly, you may want to add a index field to your Core Data model, and update it with the index of cell every time the cell is moved.
So you can populate the data to table view in order of the index filed, and keep cell order synchronized with data model.
First of all a side note: The objects in NSManagedObjectContext are unordered so there is no method to insert an object at an particular index.
Since the object is inserted already two lines above in the method NSManagedObject(entity:insertIntoManagedObjectContext:), delete the line which causes the error.
Agreed with Vadian, you do not need to worry about ordering the Managed Object Context.
To update the UI you should run -
"tableView.reloadData"
in addition to above code.

Delete items from tableview row and core data simultaneously

I'm trying to delete items from a TableView and an entity called "Books." I have no idea if I'm remotely on the right track, however. When I try this code:
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
var appdel:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var context:NSManagedObjectContext = appdel.managedObjectContext!
var request = NSFetchRequest(entityName: "Books")
if editingStyle == UITableViewCellEditingStyle.Delete {
addBook.myBooks.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
context.deleteObject(addBook.myBooks[indexPath.row] as! NSManagedObject)
}
}
I get a warning on the last line that says "Cast from 'String' to unrelated type 'NSManagedObject' always fails." Does anyone know how I can get around this? I've read that you can use a fetchedResultsController to handle core data in tables easily but I am new to programming and found that method a bit more confusing when setting up core data in general. Is the fetchedResultsController necessary to manage the data in my TableView?
From your error it sounds like addBook.myBooks is an array of strings.
The immediate problem is that deleteObject doesn't work on strings, it works on managed objects-- that is, instances of NSManagedObject or a subclass of NSManagedObject. You can't delete a string from Core Data like that, you have to delete the managed object that corresponds to the string. The error is specifically telling you that as! NSManagedObject doesn't work on a string, because a string is a completely different kind of thing from a managed object.
[It's also a problem that you're removing the string at indexPath.row via removeAtIndex, and then later trying to use the string at indexPath.row that you just removed, but that's not the real problem here.]
What you need to do is find out the managed object that corresponds to the table view row you're deleting, and pass that to deleteObject. Without a fuller picture of how your view controller works it's impossible to say exactly how you would do that, but there are a couple of things that are clear:
Those first three lines in your method are not doing anything useful. Cut them-- even if you made them work, they'd be the wrong approach here. You don't want to have to fetch the managed object you're deleting right here. By the time you reach this method you should already know enough to delete it.
It's not necessary to use NSFetchedResultsController to put Core Data together with table views. But if you're new to programming you'll probably find things a lot easier if you use it.
Try to save your context after deleteObject
context.deleteObject(addBook.myBooks![indexPath.row])
addBook.myBooks.removeAtIndex(indexPath.row)
do {
try context.save()
}
catch {
print("Error")
}
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
}

Why is my Swift for-in-loop not executing/iterating?

I'm trying to delete an array of NSManagedObjects associated with a CoreData model. I'm creating a new instance of the class my property is apart of, and attempting to delete each item within. Any clues on what I'm doing wrong?
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// THIS FOR IN LOOP WILL NOT EXECUTE...
for x in DrilledDownCategoryViewController().categoryItemsItems {
println("^^^")
managedObjectContext?.deleteObject(x as NSManagedObject)
}
// EVERYTHING ELSE WILL THOUGH...
println("asdfjkl;")
let logItemToDelete = categoryItems[indexPath.row]
managedObjectContext?.deleteObject(logItemToDelete)
self.fetchCategory()
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
save()
}
}
DrilledDownCategoryViewController is a class.
DrilledDownCategoryViewController() is its contructor.
With DrilledDownCategoryViewController() you create a new instance of your view controller rather than accessing the "running" one.
Naturally a new Object's related array (or what ever) would be either nil or what it was set to in its constructor.
If you are used to Objective-C, this is what you did:
[[[DrilledDownCategoryViewController alloc] init| categoryItemsItems]
An Obj-C forin loop woult not iterate a single time too.
The for-in-loop will not execute an empty array.
Just do this so you don't run into an empty fetch results array from another class: If you're trying to access a fetched results array of CoreData items; don't depend on accessing an array from another class. Just fetch particular predicate filtered items and inject them into a new array. You'll be able to alter or delete them from there...

Resources