TableView editingStyle

How do I code a specific array element in section (GroupBy)?

in editingStyle, lists.remove(at: indexPath.row) moved the wrong element.

Help..

Answered by Claude31 in 712123022

what is the difference between following two ones code?

  • sections[indexPath.section].trades.remove(at: indexPath.row)

and

  • self.trades.removeAll(where : { $0.id == sections[indexPath.section].trades[indexPath.row].id }) // MY MISTAKE: need .id at the end of course

You have to understand what are your data structures.

  • self.trades is the original array of Trade with all the original items
  • sections is an array of sectionBySymbol, each having an array of Trade, which is a subset of self.trades
  • sections is computed from self.trades grouping by symbol; its trades include only those items in self.trades that match their symbol.

In

sections[indexPath.section].trades.remove(at: indexPath.row)
  • you remove an item from a given section trades ; but you leave self.trades untouched.
  • Hence, if you recompute sections with grouping by symbol, you do this from the original untouched self.trades array. Hence, any item you may have removed in a section will reappear.

In

self.trades.removeAll(where : { $0.id == sections[indexPath.section].trades[indexPath.row].id }) // .id at the end

$0.id is the id of a Trade item in self.trades ; sections[indexPath.section].trades[indexPath.row].id is the id of an item in the section from which you remove item.

  • you remove item directly from self.trades (those which id matches the one of removed item in table : sections[indexPath.section].trades[indexPath.row])
  • So now, if you recompute sections, the removed item is no more in self.trades and thus will not reappear in any section.

Note: things could become more clear if you changed the name of property:

struct sectionBySymbol {
    var symbol: String
    var sectionTrades: [Trade]  // Renamed
}

Then

self.trades.removeAll(where : { $0.id == sections[indexPath.section].trades[indexPath.row].id })

becomes

self.trades.removeAll(where : { $0.id == sections[indexPath.section].sectionTrades[indexPath.row].id })

Hope that helps.

Could you show the code you have so far and explain exactly what does not work as expected ?

Have you several sections in the table ?

If so, your data source should manage the 2 dimensions: section and row in section.

So, lists cannot be the dataSOurce. It should be more a 2D array:

var lists : [[someType]] // To be populated to feed rows in each section

And then, you should call:

lists[indexPath.section].remove(at: indexPath.row)

Claude31: I am still confused. Since my code is short, I will post is below for you to help pointing out the mistakes.. Code crashes when remove element from array and delete from table.. Thank You!

//data model

import Foundation

//Trade data model

struct Trade {

    var id: Int

    var date: Date

    var symbol: String

    var qty: Double

    var price: Double

}

//Group Trade by symbol for tableView Section

struct sectionBySymbol {

    var symbol: String

    var trades: [Trade]

}



//code 

import UIKit



class SectionTableViewController: UITableViewController {



    //for Sections

    var sections = [sectionBySymbol]()

    

    var trades: [Trade] = [

        Trade(id: 1, date: stringToDate("04/18/2022 10:05"), symbol: "TSLA", qty: 10, price: 989.20),

        Trade(id: 2, date: stringToDate("04/19/2022 11:30"), symbol: "TSLA", qty: 20, price: 970),

        Trade(id: 3, date: stringToDate("04/20/2022 08:13"), symbol: "NVDA", qty: 50, price: 220.25),

        Trade(id: 4, date: stringToDate("04/20/2022 09:23"), symbol: "AFRM", qty: 25, price: 40.25),

        Trade(id: 5, date: stringToDate("04/19/2022 07:20"), symbol: "AFRM", qty: 30, price: 32.95),

        Trade(id: 6, date: stringToDate("04/17/2022 11:21"), symbol: "IBM", qty: 25, price: 110.25),

        Trade(id: 7, date: stringToDate("04/20/2022 09:23"), symbol: "IBM", qty: 25, price: 112.00),

        Trade(id: 8, date: stringToDate("04/19/2022 07:20"), symbol: "NFLX", qty: 40, price: 222.15),

        Trade(id: 9, date: stringToDate("04/17/2022 11:21"), symbol: "NVDA", qty: 100, price: 199.25),

        Trade(id: 10, date: stringToDate("04/20/2022 09:23"), symbol: "TSLA", qty: 25, price: 1110.25),

        Trade(id: 11, date: stringToDate("04/19/2022 07:20"), symbol: "AFRM", qty: 30, price: 34.00),

        Trade(id: 12, date: stringToDate("04/17/2022 11:21"), symbol: "IBM", qty: 25, price: 99.80)

    ]

    

    override func viewDidLoad() {

        super.viewDidLoad()



        //set title

        navigationItem.title = "Cost Average"

        navigationItem.leftBarButtonItem = editButtonItem

     

        let groupBySymbol = Dictionary(grouping: trades, by: \.symbol)      

        self.sections = groupBySymbol.map {(Key, Value) in

            return sectionBySymbol(symbol: Key, trades: Value)

        }

   }



    // MARK: - Table view data source

    

    //numberOfSections

    override func numberOfSections(in tableView: UITableView) -> Int {

        // return number of sections

        return self.sections.count  //number of section

    }

    

    //numberOfRowsInSection

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return sections[section].trades.count

    }

    

    //cellForRowAt

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)

        

        //decimal number with comma

        let formatter = NumberFormatter()

        formatter.numberStyle = .decimal

        

        var content = cell.defaultContentConfiguration()

        let section = self.sections[indexPath.section]

        let trade = section.trades[indexPath.row]



        content.text = "\(trade.symbol) \(String(format: "%.0f", trade.qty)) x $\(String(format: "%.2f", trade.price)) = \(formatter.string(from: NSNumber(value: trade.qty * trade.price)) ?? "0.00")"

        content.secondaryText = "\(dateToString(trade.date))"

        

        cell.contentConfiguration = content

        return cell

    }



    // viewForHeaderInSection to create Section Header

    

    override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

        

        let titleLabel = UILabel()

        titleLabel.text = "Test"

        titleLabel.backgroundColor = UIColor.gray

        return (titleLabel)

    }

    

    // Override to support editing the table view.

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {



        if editingStyle == .delete {

            trades.remove(at: indexPath.row)

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

        }

    }

    

    // Override to support rearranging the table view.

    override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {

        let movedTrade = trades.remove(at: fromIndexPath.row)

        trades.insert(movedTrade, at: to.row)

    }



}

Here is the error:

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {

        if editingStyle == .delete {
            trades.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .automatic)
        }

    }

You remove item for the full trades array. But not from the trades in

struct sectionBySymbol {
    var symbol: String
    var trades: [Trade]
}

Which causes the number of rows in section not to be correct anymore.

Just change as follows:

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {

        if editingStyle == .delete {
            sections[indexPath.section].trades.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .automatic)
        }

    }

You'd better close this thread about removing, and create a new one (referencing this one if needed).

However, answer is about the same.

You need to add the element in the dataSource.

But here, you need to find in which section to add, eventually creating a new section.

It may thus be simpler to:

  • append to self.trades
  • recompute the section, by calling
        let groupBySymbol = Dictionary(grouping: trades, by: \.symbol)      
        self.sections = groupBySymbol.map {(Key, Value) in
            return sectionBySymbol(symbol: Key, trades: Value)
        }
  • reload the tableView.

Thanks for feedback. It is time now to close the thread.

I'm running into an interesting problem: After Add working by calling grouping.., but tableView will bring all deleted element back

That's logical with the code:

  • you delete rows in sections[indexPath.section]
        if editingStyle == .delete {
            sections[indexPath.section].trades.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .automatic)
        }

But you do not update self.trades. So when you add an item, you add it to the original self.trades (no item removed) ; when rebuild sections, your rebuild with all initial items plus the new ones…

So, you should remove from self.trades as well:

        if editingStyle == .delete {
            self.trades.removeAll(where : { $0.id == sections[indexPath.section].trades[indexPath.row] })   // do it before next line
            sections[indexPath.section].trades.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .automatic)
        }

You could also unify your code by updating self.trades and rebuild sections, but the previous one avoids rebuilding everything:

        if editingStyle == .delete {
            self.trades.removeAll(where : { $0.id == sections[indexPath.section].trades[indexPath.row] })   // do it before next line
            let groupBySymbol = Dictionary(grouping: trades, by: \.symbol)      
            self.sections = groupBySymbol.map {(Key, Value) in
                 return sectionBySymbol(symbol: Key, trades: Value)
            }
            tableView.deleteRows(at: [indexPath], with: .automatic)
        }

Note: Dictionary is not ordered. Order of cells may change when sections are rebuilt. Otherwise, you will have to manage to get sections in right order (with a sort) and have items in each section in the correct order.

To get a constant sections order, you could create rebuildSections func to sort in ascending order of symbol:

func rebuildSections() {
    let groupBySymbol = Dictionary(grouping: trades, by: \.symbol)
    sections = groupBySymbol.map {(Key, Value) in
        return sectionBySymbol(symbol: Key, trades: Value)
    }
    sections = sections.sorted(by: { $0.symbol < $1.symbol })
}
Accepted Answer

what is the difference between following two ones code?

  • sections[indexPath.section].trades.remove(at: indexPath.row)

and

  • self.trades.removeAll(where : { $0.id == sections[indexPath.section].trades[indexPath.row].id }) // MY MISTAKE: need .id at the end of course

You have to understand what are your data structures.

  • self.trades is the original array of Trade with all the original items
  • sections is an array of sectionBySymbol, each having an array of Trade, which is a subset of self.trades
  • sections is computed from self.trades grouping by symbol; its trades include only those items in self.trades that match their symbol.

In

sections[indexPath.section].trades.remove(at: indexPath.row)
  • you remove an item from a given section trades ; but you leave self.trades untouched.
  • Hence, if you recompute sections with grouping by symbol, you do this from the original untouched self.trades array. Hence, any item you may have removed in a section will reappear.

In

self.trades.removeAll(where : { $0.id == sections[indexPath.section].trades[indexPath.row].id }) // .id at the end

$0.id is the id of a Trade item in self.trades ; sections[indexPath.section].trades[indexPath.row].id is the id of an item in the section from which you remove item.

  • you remove item directly from self.trades (those which id matches the one of removed item in table : sections[indexPath.section].trades[indexPath.row])
  • So now, if you recompute sections, the removed item is no more in self.trades and thus will not reappear in any section.

Note: things could become more clear if you changed the name of property:

struct sectionBySymbol {
    var symbol: String
    var sectionTrades: [Trade]  // Renamed
}

Then

self.trades.removeAll(where : { $0.id == sections[indexPath.section].trades[indexPath.row].id })

becomes

self.trades.removeAll(where : { $0.id == sections[indexPath.section].sectionTrades[indexPath.row].id })

Hope that helps.

To close the thread, just mark the correct answer by tapping on the checkmark in the lower circle.

It will turn green:

on moveRowAt, I can move element within section: 

let movedTrade = sections[fromIndexPath.section].sectionTrades.remove(at: fromIndexPath.row)     sections[to.section].sectionTrades.insert(movedTrade, at: to.row) 

How do I move in self.trades?

So you move in the same section (changing section would not make sense).

  • Which means fromIndexPath.section == to.section
  • Is it what you have?
  • it means you want to reorder ?
  • Order is computed automatically, sorted by growing ID.
  • So, if you want to do it, you will have to update the id for all items in the section.

For instance, you had.

    Trade(id: 4, date: stringToDate("04/20/2022 09:23"), symbol: "AFRM", qty: 25, price: 40.25),
    Trade(id: 5, date: stringToDate("04/19/2022 07:20"), symbol: "AFRM", qty: 30, price: 32.95),
    Trade(id: 11, date: stringToDate("04/19/2022 07:20"), symbol: "AFRM", qty: 30, price: 34.00),

Imagine you want to move third in first position.

Then, ids: [4, 5, 11], should now be in other order [11, 4, 5]. You need to keep those id, to avoid smashing ids in other sections.

Which means,

Trade(id: 11, date: stringToDate("04/19/2022 07:20"), symbol: "AFRM", qty: 30, price: 34.00),

becomes

Trade(id: 4, date: stringToDate("04/19/2022 07:20"), symbol: "AFRM", qty: 30, price: 34.00),

and

    Trade(id: 4, date: stringToDate("04/20/2022 09:23"), symbol: "AFRM", qty: 25, price: 40.25),

becomes

    Trade(id: 5, date: stringToDate("04/20/2022 09:23"), symbol: "AFRM", qty: 25, price: 40.25),

and

    Trade(id: 5, date: stringToDate("04/19/2022 07:20"), symbol: "AFRM", qty: 30, price: 32.95),

becomes

    Trade(id: 11, date: stringToDate("04/19/2022 07:20"), symbol: "AFRM", qty: 30, price: 32.95),

To code this: var oldIdInPositions : [Int] = [] var newIdInPositions : [Int] = [] // Get the old values if fromIndexPath.section == to.section { return } // Should do nothing, cannot occur

oldIdInPositions = sections[to.section].sectionTrades.map { $0.id } newIdInPositions = oldIdInPositions // at start

newPos.move(fromOffsets: [fromIndexPath.row], toOffset: to.row) // We just move in newPos

Now we can change in self.trades. Do it using index in trades, not a copy of trade item. Needed to update trades

for index in 0..<self.trades.count where self.trades[index].symbol == sections[to.section].sectionTrades[0].symbol {  // all sectionTrades[x] have the same symbol in a section, just choose the first
    // What is the position of self.trades[index] in section ? We search its id in oldIdInPositions
    if let pos = oldIdInPositions.firstIndex(of: self.trades[index].id) {    // We found it at pos
           // Then, update the id, with the new value
          self.trades[index].id == newIdInPositions[pos]
        }
}

Now, rebuild sections

rebuildSections()

I did not test, hope that works.

TableView editingStyle
 
 
Q