2

I have a problem in my UITableView which filled with data: [(gamme: String, [(product: String, quantity: Double)])], everything works fine: inserting rows and sections, deleting row and section, reloading. But sometimes and when I try to delete lots of lines in fast way (line by line by swiping the line the table and tap (-) ). it leads to a crash like in the screenshot.

The issue is hard to reproduce in development app. but my clients still reports it. My clients are professionals (not normal users) and are expected to use the in a fast way with medium to large data.

enter image description here

and this is my func that delete lines:

    override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {

    let delete = UITableViewRowAction(style: .destructive, title: "-") { (action, indexPath) in

        let cmd = self.groupedData[indexPath.section].1.remove(at: indexPath.row)
        tableView.deleteRows(at: [indexPath], with: .right)
        self.delegate?.didDeleteCmdLine(cmd)

        if self.groupedData[indexPath.section].1.count == 0 {
            self.groupedData.remove(at: indexPath.section)
            tableView.deleteSections(IndexSet(integer: indexPath.section), with: UITableViewRowAnimation.right)
        }
    }

    return [delete]
}

why is that happening ?

This is a screen of xcode organiser for the crash

enter image description here Edit:

Checking if groupedData is accessed by any thread other than main proposed by @Reinhard:

private var xgroupedData = [(gamme: GammePrdCnsPrcpl, [cmdline])]()

private var groupedData: [(gamme: GammePrdCnsPrcpl, [cmdline])] {
    get {
        if !Thread.isMainThread {
            fatalError("getting from not from main")
        }
        return xgroupedData
    }
    set {
        if !Thread.isMainThread {
            fatalError("setting from not from main")
        }
        xgroupedData = newValue
    }
}

but the groupedData variable is accessed only from main thread

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
Siempay
  • 876
  • 1
  • 11
  • 32
  • Why are you deleting an entire section? – El Tomato Oct 01 '18 at 12:12
  • If the section (gamme) has no more products, it has to be deleted too – Siempay Oct 01 '18 at 12:30
  • @brahimm Is that a real screenshot of the production version of your app? If so, you might want to get rid of the API key in there! – dlggr Oct 20 '18 at 19:23
  • @dnlggr thanks man, but no that one is just for dev – Siempay Oct 20 '18 at 19:31
  • I think it could be you are copying index paths in to the row action closure. Try and copy the model object in to the block instead, then recalculate the section index that you're removing inside the closure itself. – josh-fuggle Oct 23 '18 at 11:15
  • I think what could be happening is: 1) You're copying a reference to an indexpath 2) Deleting said section, thereby offsetting all subsequent sections who previously thought they were one section greater than they now are, 3) Then accessing a copied version of a now invalidated indexpath and trying to do work with it, potentially leading to an out of bounds style exception. ... You can probably confirm by: 1) Scroll to bottom of table view 2) Delete the second last section, 3) Delete the last section. 4) Crash? – josh-fuggle Oct 23 '18 at 11:23
  • I tested it as you said but no crash, I tries as hard as I could but couldn't get it to crash, in the way you said, and any other possible way. but still getting crash reports – Siempay Oct 23 '18 at 14:49
  • I generally use collection view rather than table view. Does `override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]?` get called at time of swipe, or at time of cell appearance? If at time of swipe then speed may be a factor as the indexpath isn't copied into the block until when the swipe starts. I think it is still possible for invalid index paths to be copied if the animation of the previous delete action is still ongoing. – josh-fuggle Oct 23 '18 at 22:30
  • What happens when you: 1) Swipe to reveal the actions drawer (but don't delete). 2) Swipe the preceding cell and delete it. 3) Swipe the cell from step 1 and this time do delete it. 4) Crash? – josh-fuggle Oct 23 '18 at 22:34
  • Hi, the func is called on swipe, so I swiped an other cell, the indepxPath will changes on each swipe. I tried what you said but nothing happened. as I said it is hard to repreduce it, we still dont know what maneuver generate the crash – Siempay Oct 24 '18 at 09:00
  • Since your app crashes, it seems that you did not set an exception breakpoint. With an exception breakpoint, your app would stop where the exception occurs, and you should get better information in the log. – Reinhard Männer Oct 27 '18 at 10:21

7 Answers7

6
tableView.beginUpdates()
self.tableView.deleteRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
Ali Ihsan URAL
  • 1,894
  • 1
  • 20
  • 43
  • Hi, thanks for the replay. Somehow this worked !! can you elaborate – Siempay Oct 01 '18 at 12:37
  • When you reload all tableview , sometimes error is occured because resuseable cells. This may be due to several reasons ( Data count , nil value etc. ). But now you only reload specific cell . If this a true answer for you , please mark as accepted. – Ali Ihsan URAL Oct 01 '18 at 12:44
  • the problem still occures! if I try to delete fastly is crashes the same way. is there any other ideas? – Siempay Oct 02 '18 at 09:51
  • did you try to freeze tableview scroll when delete action runs ? – Ali Ihsan URAL Oct 02 '18 at 10:05
  • what do you mean? no I did not do anything with the scroll – Siempay Oct 02 '18 at 10:23
  • The error occures when try to delete fastly ?. Before the deleteRow & deleteData action , freeze tableView Scroll https://stackoverflow.com/questions/3410777/how-can-i-programmatically-force-stop-scrolling-in-a-uiscrollview – Ali Ihsan URAL Oct 02 '18 at 10:31
  • @AliIhsanURAL and why should this be the answer? – Siempay Oct 02 '18 at 14:48
4

Changes in João Luiz Fernandes answer....try this

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
                if editingStyle == .delete {
                    objects.remove(at: indexPath.row)
                    tableView.deleteRows(at: [indexPath], with: .fade)
                } 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.
                }
            }

reference (Hacking with swift . https://www.hackingwithswift.com/example-code/uikit/how-to-swipe-to-delete-uitableviewcells )

Rajan Singh
  • 202
  • 2
  • 7
1

u can try this

         var myDataArray = ["one","two","three"]
         //wanna insert a row then in button action or any action write
         myDataArray.append("four")
         self.tblView.reloadData()
         // wanna delete a row
         myDataArray.removeObject("two")
         // you can remove data at any specific index liek
         //myDataArray.remove(at: 2)
         self.tblView.reloadData()
Rajan Singh
  • 202
  • 2
  • 7
  • Hi, thanks for the replay, you actually should do `tblView.insertRow(indexPath)` when appending a value to data. because `reloadData()` will refresh all the tableView which is no necessary – Siempay Oct 20 '18 at 12:49
  • @brahimm then use tblview.reloadrows at indexpath which reload yr recent row then your all data will not reload – Rajan Singh Oct 20 '18 at 15:01
0

try to use like this function.

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        print("Deleted")

        // do your delete your entires here..
        //

        self.tableView.deleteRows(at: [indexPath], with: .automatic)
    }
}
0

After the user tapped the delete button, you remove the corresponding row and (if this was the last row of that section) the corresponding section from your data source groupedData, which is an array. However, array operations are not thread-safe.
Can it be that another thread is using this array while it is modified by the delete action? In this case, the app can crash. The danger is of course higher, when several actions are triggered in a short time, which seems to be the case as you described it.
One way (maybe not the best) to avoid multithreading problems is to access the array only on the main thread.
If this slows down the main thread, one can use a synchronised array that allows multiple reads concurrently, but only a single write that blocks out all reads, see here.

Reinhard Männer
  • 14,022
  • 5
  • 54
  • 116
0

There is just a update @codeByThey's answer.

Please Update your DataSource file as you delete that particular row.

tableView.beginUpdates()
self.whatEverDataSource.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .automatic)
tableView.endUpdates()

This will result that your DataSource is also update same time as the TableView. The crash is happening may be due to the DataSource is not updated.

Mayank Verma
  • 108
  • 1
  • 8
0

Can you try removing the section at once instead of trying to remove the row once, and then the section which it belongs to, when the last item in a section is being removed?

func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {

    let delete = UITableViewRowAction(style: .destructive, title: "-") { [weak self] (action, indexPath) in

        // Use a weak reference to self to avoid any reference cycles.
        guard let weakSelf = self else {
            return
        }

        tableView.beginUpdates()

        let cmd: cmdline
        if weakSelf.groupedData[indexPath.section].1.count > 1 {
            cmd = weakSelf.groupedData[indexPath.section].1.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .right)
        } else {
            // Remove the section when the `items` are empty or when the last item is to be removed.
            cmd = weakSelf.groupedData.remove(at: indexPath.section).1[indexPath.row]
            tableView.deleteSections([indexPath.section], with: .right)
        }
        weakSelf.delegate?.didDeleteCmdLine(cmd)

        tableView.endUpdates()
    }

    return [delete]
}
D V
  • 348
  • 2
  • 10