0

I have the following class:

ChatMessage: Codable {
    var id: Int?
    var sender: User?
    var message: String?
    var seen: Int?
    var tsp: Date?
}

The tsp is formatted like this: YYYY-MM-DD hh:mm:ss

I would like to "group" messages sent on the same day to end up with something like in this example:

let groupedMessages = [ [ChatMessage, ChatMessage], [ChatMessage, ChatMessage, ChatMessage] ]

I ultimately want to use groupedMessages in a UITableViewController to introduce sections like so:

func numberOfSections(in tableView: UITableView) -> Int {
        return groupedMessages.count 
        // would be 2 int the above
    }



    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return chatMessages[section].count 
        // would be 2 for the first and 3 for the second section
    }

What would be the most performant way of getting the sorting done? - i.e. something that also works well once the number of chatMessages to be sorted increases

Bernhard Engl
  • 243
  • 1
  • 2
  • 12

4 Answers4

2

You could try:

let cal = Calendar.current
let groupedMessages = Dictionary(grouping: self.chatMessages, by: { cal.startOfDay($0.tsp) })
let keys = groupedMessages.keys.sorted(by: { $0 > $1 })

This however would give you a dictionary like:

[
  SomeDateHere: [
    ChatMessage(id: 0, sender: User?, message: "String", seen: 1),
    ChatMessage(id: 0, sender: User?, message: "String", seen: 1)
  ],
  AnotherDateHere: [
    ChatMessage(id: 0, sender: User?, message: "String", seen: 1)
]

You could then use keys to return the section count:

return keys.count

And to get the array of messages for each dictionary item like so:

let key = keys[indexPath.section]
let messages = groupedMessages[key].sorted(by: { $0.tsp > $1.tsp })

Or to get just the count:

let key = keys[indexPath.section]
return groupedMessages[key].count
DrewG23
  • 427
  • 3
  • 11
  • looks like a neat solution. But a dictionary seems less optimal for usage in the UITableView sections. At least I don't know how / with whatI would replace `return groupedMessages.count` and `return chatMessages[section].count` in my code to get it do work again. How would I do that without knowing the dictionary's keys? – Bernhard Engl Jan 15 '20 at 15:20
  • The grouping will create the keys for you. Then you can use them like in the example I provided above. – DrewG23 Jan 15 '20 at 18:17
  • Awesome - that works nice!!! One final, quick question: The api I'm calling returns newest chatmessages first, which makes sense. But when I simply dequeue them in cellForRowAt, it will show the oldest message at the bottom of the screen - i.e. in exactly the wrong order. Is ther a quick way to just flip the sorted dictionary on its head - or alternatively the messages array, prior to feeding it to the sorting closure? many thanks for your help!!! – Bernhard Engl Jan 17 '20 at 15:40
  • Just updated my answer to show you how to sort the messages in each section. If the items are not in the order that you like, you can play with and change the way it sorts by changing `>` to `<`. – DrewG23 Jan 17 '20 at 15:57
  • Works like a charmer ;-) Thank you sooo much for your time and help!!! – Bernhard Engl Jan 19 '20 at 09:49
  • No problem! Thanks for making it as the answer! – DrewG23 Jan 19 '20 at 19:06
0

Grouping array by date you could find here How to group array of objects by date in swift?

How to connect this to UITableView you will find here https://www.ralfebert.de/ios-examples/uikit/uitableviewcontroller/grouping-sections/

0

I would probably start by introducing a new type:

public struct CalendarDay: Hashable {
   public let date: Date

   public init(date: Date = Date()) {
       self.date = Calendar.current.startOfDay(for: date)
   }
}

extension CalendarDay: Comparable {
   public static func < (lhs: CalendarDay, rhs: CalendarDay) -> Bool {
      return lhs.date < rhs.date
   }
}

Then I would extend your message:

extension ChatMessage {
    var day: CalendarDay {
       return CalendarDay(date: tsp)
    }
}

(I am assumming tsp is not an optional, there is no reason for it to be optional).

Now, let's define a section:

struct Section {
   var messages: [ChatMessage] = []
   let day: CalendarDay

   init(day: CalendarDay) {
      self.day = day
   }
}

Let's group easily:

let messages: [ChatMessage] = ...
var sections: [CalendarDay: Section] = [:]

for message in messages {
   let day = message.day
   var section = sections[day] ?? Section(day: day)
   section.messages.append(day)
   sections[day] = section
}

let sectionList = Array(sections.values).sorted { $0.day < $1.day }

If your messages are not originally sorted, you might need to also sort messages in every section separately.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
0
let testArray = ["2016-06-23 09:07:21", "2016-06-23 08:07:21", "2016-06-22 09:07:21", "2016-06-22 08:07:21"]

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "YYYY-MM-DD hh:mm:ss"

        let groupDic = Dictionary(grouping: testArray) { (pendingCamera) -> DateComponents in

            let date = Calendar.current.dateComponents([.day, .year, .month], from: dateFormatter.date(from: pendingCamera)!)

            return date
        }
        print(groupDic)

You can take groupDic and return in numberOfSections and titleForHeaderInSection.

halfer
  • 19,824
  • 17
  • 99
  • 186
TheCodeTalker
  • 685
  • 6
  • 14