how to divide array of objects to arrays depend on date property for each object

I have a array of posts, I want to sort this array to many arrays, each array include the posts that related to same date of post.

Accepted Reply

Problem is line 95:


                var postsInThisDate:Array = self.postDictionary[postsDate]!

You create a new array.


So, when you removeRow, you remove on the copy, not on the data source.

Hence, it is inconsitent with the tableView, hence the crash.


In addition, take care with self.postDictionary[postsDate]!, which could be nil


Replace by:


func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
     
        let deleteAction = UITableViewRowAction(style: .default, title: "Delete", handler: { (action, indexPath) in
                tableView.beginUpdates()
            
                let postsDate = self.datesOfPostsForTitles[indexPath.section]
               // REMOVE  var postsInThisDate:Array = self.postDictionary[postsDate]!
             
                if self.postDictionary[postsDate] != nil && self.postDictionary[postsDate]!.count > 0  {
                  self.postDictionary[postsDate]!.remove(at: indexPath.row)
                  tableView.deleteRows(at: [indexPath], with: .fade)
                  tableView.endUpdates()
                }
            }


In fact you could better check against the indexPath.row

                if self.postDictionary[postsDate] != nil && self.postDictionary[postsDate]!.count > indexPath.row  {


Check also if your cancel from Alert is really taken into account. I've not looked in detail, but I fear not with such code (deletion could occur before alert is replied)

Replies

I assume the date is expressed as a String


I would create a dictionary:

var allPostsDict = [String: [String]]()


Read each post from the initial array

Get its date and message (String and String)

Then add it to the correct item in dictionary

if allPostsDict[date] == nil {
     allPostsDict[date] = [message]     // Create an entry for this key
} else {
     allPostsDict[date]!.append(message)  // append to the existing array for this key
}


You can at the end get all the arrays in an array of arrays, but it is better to keep as dictionary.

I already create a dictionary for this hierarchy , actually I used posts in tableview, each section include a group of posts depend date, each section header title is a date that associate with dates of this group.

but when I try delete a row , error occured



'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'


my code:




var datesOfPostsForTitles = [String]()
var postDictionary = [String:[Post]]()


override func viewDidLoad()
{

for post in self.posts
            {

                let dateString = post.date
                if var postsInThisDate = self.postDictionary[dateString]
                {
                    postsInThisDate.append(post)
                    self.postDictionary[dateString] = postsInThisDate
                }
                else
                {
                    self.postDictionary[dateString] = [post]
                }
            }

            self.datesOfPostsForTitles = [String](self.postDictionary.keys)
            self.datesOfPostsForTitles.sort(by: {self.convertStringToDate(stringDate: $0) > self.convertStringToDate(stringDate: $1) })
            self.tableView.reloadData()



}


 
func convertStringToDate(stringDate : String)->Date
    {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        let date = dateFormatter.date(from:stringDate )
        return date!
    }




func numberOfSections(in tableView: UITableView) -> Int {
        return datesOfPostsForTitles.count
    }


 
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return datesOfPostsForTitles[section]
    }

 

 
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
        let postsDate = datesOfPostsForTitles[section]
        
        if let postsInThisDate = postDictionary[postsDate]
        {
            return postsInThisDate.count
        }
        return 0
    }

 

 
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! ReservationViewCell
        
        
        let postsDate = datesOfPostsForTitles[indexPath.section]
        if let postsInThisDate = postDictionary[postsDate]
        {
         eachPost = postsInThisDate[indexPath.row]
         cell.labelDate.text = eachPost.date
        }
        return cell
    }

 

 
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
        
        let deleteAction = UITableViewRowAction(style: .default, title: "Delete", handler: { (action, indexPath) in
            print("delete passed")
            
                tableView.beginUpdates()
               
                let postsDate = self.datesOfPostsForTitles[indexPath.section]
                var postsInThisDate:Array = self.postDictionary[postsDate]!
                
                   if postsInThisDate.count > 0
                {
                postsInThisDate.remove(at: indexPath.row)
                tableView.deleteRows(at: [indexPath], with: .fade)
                tableView.endUpdates()
                    
                }
            }
       
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertAction.Style.cancel)
            {
              UIAlertAction in
              NSLog("Cancel deleted")
            }
            // Add the actions of delete
             alert.addAction(okAction)
             alert.addAction(cancelAction)
            self.present(alert, animated: false)   
        })
        return [deleteAction]
    }

I’ll let Claude31 tackle your table view issue (thanks Claude31!) but I want to address your date handling. Your

convertStringToDate(stringDate:)
function is concerning for two reasons:
  • You’re using a fixed-format date string but not constraining the locale. If the user is using a non-Gregorian calendar, this won’t end well.

    For more background on this, see QA1480 NSDateFormatter and Internet Dates.

  • You’re re-creating your

    DateFormatter
    instance each time, which is not a quick operation. If you’re going to be using the same date formatter over and over again, cache it.

Taking a step back, your overall strategy seems to involve tracking days using

yyyy-MM-dd
strings. Are you doing that because that’s how your back-end system works? Or are you starting with a
Date
value and then converting each of those to a string yourself?

Finally, I just want to point out that you can create a dictionary by grouping values using the

Dictionary(grouping:by:)
initialiser. Thus you can replace lines 8 through 21 of your code with line 13 from the following example.
struct Post {
    var dateString: String
    var name: String
}

let posts = [
    Post(dateString: "2018-01-31", name: "Luke"),
    Post(dateString: "2018-01-31", name: "Leia"),
    Post(dateString: "2018-01-30", name: "Han"),
    Post(dateString: "2018-01-30", name: "Chewy"),
    Post(dateString: "2018-01-31", name: "Lando"),
]
let postsByDate = Dictionary(grouping: posts) { $0.dateString }

for (postDate, postsInThisDate) in postsByDate {
    print(postDate)
    print(postsInThisDate.map { $0.name })
}
// prints:
//
// 2018-01-30
// ["Han", "Chewy"]
// 2018-01-31
// ["Luke", "Leia", "Lando"]

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Problem is line 95:


                var postsInThisDate:Array = self.postDictionary[postsDate]!

You create a new array.


So, when you removeRow, you remove on the copy, not on the data source.

Hence, it is inconsitent with the tableView, hence the crash.


In addition, take care with self.postDictionary[postsDate]!, which could be nil


Replace by:


func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
     
        let deleteAction = UITableViewRowAction(style: .default, title: "Delete", handler: { (action, indexPath) in
                tableView.beginUpdates()
            
                let postsDate = self.datesOfPostsForTitles[indexPath.section]
               // REMOVE  var postsInThisDate:Array = self.postDictionary[postsDate]!
             
                if self.postDictionary[postsDate] != nil && self.postDictionary[postsDate]!.count > 0  {
                  self.postDictionary[postsDate]!.remove(at: indexPath.row)
                  tableView.deleteRows(at: [indexPath], with: .fade)
                  tableView.endUpdates()
                }
            }


In fact you could better check against the indexPath.row

                if self.postDictionary[postsDate] != nil && self.postDictionary[postsDate]!.count > indexPath.row  {


Check also if your cancel from Alert is really taken into account. I've not looked in detail, but I fear not with such code (deletion could occur before alert is replied)

I discover this fantastic grouping capability. Thanks.


I also read this with interest:

h ttps://ericasadun.com/2017/06/14/the-surprising-awesomeness-of-grouped-dictionaries/