Parse Delete a text and a photo in a line

Hello


In the app you can add a text and a photo to each line.

Now I want to delete the text and the photo in the line, which is clicked in the line.



@IBAction func remove(_ sender: Any) {
        
        let query = PFQuery(className: "Post")
        query.whereKey("username", equalTo: PFUser.current()?.username)
        query.findObjectsInBackground(block: { (object, error) in
            
            if let posts = object {
                for post in posts{
                    print(posts)

                    self.comments.remove(at: self.comments.index(of: post["message"] as! String)!)
                    self.imageFile.remove(at: self.imageFile.index(of: post["imageFile"] as! PFFile)!)
                   
                    self.tableView.reloadData()

    }
                
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            }
        })
    }

Message in the line 13. Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value


I'm still a beginner and I'm not going any further.


Can someone help me and show me how I need to do this delete function ?

thank you very much

best greetings

Replies

Could you show the output of

print(posts)


Error is line 13 (in fact probably 11 or 12) ?


You have a lot of unwrap ! that are not very safe.


Cause is that you unwrap a nil ! It may be:

- post has no field "message" or "imageFile" : hence post["message"] or post["imageFile"] is nil (take care to the exact spelling of message, including lower/uppercase


- the type of post["message"] is not a String, hence a crash with as! String


- the type of post["imageFile"] is not PFFile, hence a crash with as! PFFile

Note: how did you define PFFile ? Is it a String ?


- the index(of ) returns nil because not found


a good way to avoid crash is to check:


if let message = post["message"] as? String, let image = post["imageFile"] as? PFFile {
      if let messageIndex = self.comments.index(of: message), imageIndex = self.imageFile.index(of:image) {
             self.comments.remove(at: messageIndex))
             self.imageFile.remove(at: imageIndex)
       }
}

Hello Claudio31


Thank you for your answer.


I've added your part. If I click on the Delete button, nothing happens.


Have I added this incorrectly?


@IBAction func remove(_ sender: Any) {
        
        let query = PFQuery(className: "Post")
        query.whereKey("username", equalTo: PFUser.current()?.username)
        query.findObjectsInBackground(block: { (object, error) in
            
            if let posts = object {
                for post in posts{
                    print(posts)
                    
                    if let message = post["message"] as? String, let image = post["imageFile"] as? PFFile {
                        if let messageIndex = self.comments.index(of: message), let imageIndex = self.imageFile.index(of:image) {
                            self.comments.remove(at: messageIndex)
                            self.imageFile.remove(at: imageIndex)
                        }
                    }  
                    self.tableView.reloadData()

    }
                
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            }
        })
    }

With this code, I post the image and the text. This works.

@IBAction func postImage(_ sender: Any) {
        
        if let image = imageTopost.image {
            
            let post = PFObject(className: "Post")
            post["message"] = comment.text
            post.saveEventually()
            post["userId"] = PFUser.current()?.objectId
            post.saveEventually()
            post["username"] = PFUser.current()?.username
            post.saveEventually()
            if let imageData = UIImagePNGRepresentation(image){
                
                //Spinner
                let activityIndicator = UIActivityIndicatorView(frame: CGRect(x:0, y: 0, width: 50, height: 50))
                activityIndicator.center = self.view.center
                activityIndicator.hidesWhenStopped = true
                activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
                view.addSubview(activityIndicator)
                activityIndicator.startAnimating()
                UIApplication.shared.beginIgnoringInteractionEvents()
                
                
                let imageFile = PFFile(name: "image.png", data: imageData)
                post["imageFile"] = imageFile
                post.saveEventually()
                post.saveInBackground(block: { (success, error) in
                
                    //Stop Spiner
                    activityIndicator.stopAnimating()
                    UIApplication.shared.endIgnoringInteractionEvents()
                    
                    
                    if success {
                        self.displayAlert(title: "Hat super geklappt", message: "Dein Ziel wurde erfolgreich hinzugefügt")
                        self.comment.text = ""
                        self.imageTopost.image = nil
                    }else {
                        self.displayAlert(title: "Etwas ist schiefgelaufen" , message: "Dein Bild konnte leider nicht gespeichert werden. Versuche es nochmal. Dein Internet muss eingeschalten sein." )
                    }
                })
            }
        }
    }



Greetings Daniel

You should answer all the questions from previous post.


Could you show the output of

print(posts)

how did you define PFFile ?


When you click delete button, do you enter into remove ?


Add some print statements to check and tell what you get exactly on console (it will be verbose, but that should help debug):


@IBAction func remove(_ sender: Any) {
    
        print("Entered remove")     // ADD THIS
        let query = PFQuery(className: "Post")
        query.whereKey("username", equalTo: PFUser.current()?.username)
        query.findObjectsInBackground(block: { (object, error) in
        
            if let posts = object {
                    print(posts)               // ADD THIS

               for post in posts{
                    print(post)               // CHANGE THIS TO SEE INDIVIDUAL POST
                
                    if let message = post["message"] as? String, let image = post["imageFile"] as? PFFile {
                         print("message and image read", message, image)          // ADD THIS
                        if let messageIndex = self.comments.index(of: message), let imageIndex = self.imageFile.index(of:image) {
                            print("messageIndex", messageIndex)
                            self.comments.remove(at: messageIndex)
                            self.imageFile.remove(at: imageIndex)
                        }
                    }
                    self.tableView.reloadData()
              }
            
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            }
        })
    }

This is the output:


message and image read fjhsdjkfhdskj <PFFile: 0x600000451b80>

[<Post: 0x6000002a3ae0, objectId: OtH7UHh9nm, localId: (null)> {

imageFile = "<PFFile: 0x600000450860>";

message = text;

userId = VBTOwnVeED;

username = "test@test.ch";

}, <Post: 0x6000002a3f00, objectId: AoN9iybL7b, localId: (null)> {

imageFile = "<PFFile: 0x600000451430>";

message = "Text Diana";

userId = VBTOwnVeED;

username = "test@test.ch";

}, <Post: 0x6000002a3840, objectId: jL1wOLhzAj, localId: (null)> {

imageFile = "<PFFile: 0x60000044b700>";

message = "Einwenig text";

userId = VBTOwnVeED;

username = "test@test.ch";

}, <Post: 0x6000002a1ce0, objectId: uTL8P8qVF3, localId: (null)> {

imageFile = "<PFFile: 0x600000450170>";

message = text;

userId = VBTOwnVeED;

username = "test@test.ch";

}, <Post: 0x6000002a0060, objectId: JVLmJr1kYs, localId: (null)> {

imageFile = "<PFFile: 0x600000451be0>";

message = jdfnsdkfj;

userId = VBTOwnVeED;

username = "test@test.ch";

}, <Post: 0x6000002a36c0, objectId: iAAfck44OR, localId: (null)> {

imageFile = "<PFFile: 0x6000004512e0>";

message = text;

userId = VBTOwnVeED;

username = "test@test.ch";

}, <Post: 0x6000002a37e0, objectId: rPpAAPGGmq, localId: (null)> {

imageFile = "<PFFile: 0x600000451bb0>";

message = "Test text zum 123";

userId = VBTOwnVeED;

username = "test@test.ch";

}, <Post: 0x6000002a35a0, objectId: s3OO4gLEsi, localId: (null)> {

imageFile = "<PFFile: 0x600000451b80>";

message = fjhsdjkfhdskj;

userId = VBTOwnVeED;

username = "test@test.ch";

}, <Post: 0x6000002a00c0, objectId: BCImU0GeqU, localId: (null)> {

imageFile = "<PFFile: 0x600000451c40>";

message = Text;

userId = VBTOwnVeED;

username = "test@test.ch";

}]


When you click Delete, this output appears

message and image read Text <PFFile: 0x600000451c40>


A short message still appears:

entered remove

Claude is on the case of the unwrapping optional issue, so I won't comment on that.


But I will point out that calling self.tableView.reloadData() from a background thread (the first call, within the for loop) is a big no-no. You must dispatch all UIKit calls to the main thread, as you are doing with that DispatchQueue block after the loop.


On the same topic of multithreading... consider the case where the main thread is busy happily updating your table view (calling your numberOfRows, cellForRow etc. methods) at the same time as this background thread is updating your "comments" and "imageFile" data structures. If those data structures are used by the main thread, then you may need to do some additional work adding synchronization and/or moving some of this processing over to the main thread too.

Thanks for the hint. can you tell me how i can implement this? regards daniel

Good catch by junkpile


In addition, it would be better to reload data only at the end.


So, moving reloadData to the dispatch will also achieve this.