5 Replies
      Latest reply on Jan 31, 2019 5:48 AM by Claude31
      BurierA Level 1 Level 1 (0 points)

        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.

        • Re: how to divide array of objects to arrays depend on date property for each object
          Claude31 Level 8 Level 8 (5,645 points)

          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.

            • Re: how to divide array of objects to arrays depend on date property for each object
              BurierA Level 1 Level 1 (0 points)

              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]
                  }
              
              
                • Re: how to divide array of objects to arrays depend on date property for each object
                  eskimo Apple Staff Apple Staff (11,055 points)

                  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"

                  • Re: how to divide array of objects to arrays depend on date property for each object
                    Claude31 Level 8 Level 8 (5,645 points)

                    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)