0

I have an array of objects that contain Date property. I want to distribute them in UITableView sections according to their month by month order. How I can do that?

My model:

class Birthday: Object {
    @objc dynamic var name: String = ""
    @objc dynamic var date: Date = Date()
    @objc dynamic var dayLeft: Int = 0
    @objc dynamic var userImageData: Data?
}

First I use the next code with DateFormatter:

var grouped: [String: [Birthday]] = [:]

grouped = Dictionary(grouping: birthdaysList) { item -> String in
    let calendar = Calendar.current
    let components = calendar.dateComponents([.year, .month], from: item.date)
    let date = calendar.date(from: components) ?? Date(timeIntervalSince1970: 0)
    return date.monthAsString()
}

extension Date {
    func monthAsString() -> String {
            let df = DateFormatter()
            df.setLocalizedDateFormatFromTemplate("MMM")
            return df.string(from: self)
    }
}

Then I use:

struct Section {
    let month: String
    let birthdays: [Birthday]
}

    var sections: [Section] = []
    let keys = Array(grouped.keys)
    sections = keys.map({Section(month: $0, birthdays: grouped[$0]!)})

But I need to sort month names in order. January, february etc. How this can be done?

I try to return tuple from my code, but get error

Declared closure result '(String, Int)' is incompatible with contextual type 'String'

grouped = Dictionary(grouping: birthdaysList) { item -> (String, Int) in
    let calendar = Calendar.current
    let components = calendar.dateComponents([.year, .month], from: item.date)
    let date = calendar.date(from: components) ?? Date(timeIntervalSince1970: 0)
    return (date.monthAsString(), components.month!)
}
Parcker
  • 219
  • 1
  • 10
  • 1
    Related: https://stackoverflow.com/questions/52043162/how-can-i-group-fetched-dates-by-month/52043752#52043752 – vadian Dec 23 '20 at 20:22
  • Thanks. But may be you can provide code example for my case? – Parcker Dec 23 '20 at 20:31
  • Actually the linked answer contains an example. Group the array to a dictionary by year and month. – vadian Dec 23 '20 at 20:36
  • I added to my question code example you provided, but anyway cannot get how to get month names in sections. – Parcker Dec 23 '20 at 20:49

1 Answers1

1

Create a wrapper struct

struct Section {
    let month : String
    let birthdays : [Birthday]
}

then map the grouped dictionary to an array of Section

let keys = Array(grouped.keys)
let sections = keys.map({Section(month: $0, birthdays: grouped[$0]!)})

sections represent the sections, birthdays the rows, month the section header titles.


Edit:

To be able to sort the months by their ordinal number return the number in front of the month name

grouped = Dictionary(grouping: birthdaysList) { item -> String in
    let calendar = Calendar.current
    let components = calendar.dateComponents([.year, .month], from: item.date)
    let date = calendar.date(from: components) ?? Date(timeIntervalSince1970: 0)
    return  "\(components.month!)_" + date.monthAsString()
}

And modify the mapping code

let keys = grouped.keys.sorted{$0.localizedStandardCompare($1) == .orderedAscending}
let sections = keys.map({Section(month: $0.components(separatedBy:"_").last!, birthdays: grouped[$0]!)})
vadian
  • 274,689
  • 30
  • 353
  • 361
  • I get error: Cannot convert value of type '[Any]' to expected argument type '[Birthday]' – Parcker Dec 23 '20 at 22:07
  • Use Swift native collection types. – vadian Dec 23 '20 at 22:08
  • What does it mean? – Parcker Dec 23 '20 at 22:11
  • The error is not related to the given code, maybe you use `NSArray` somewhere or you forgot to downcast a type properly. – vadian Dec 23 '20 at 22:13
  • But how I can sort months in order? I want january will be first, then february etc. How this can be done? – Parcker Dec 24 '20 at 08:20
  • Rather than returning a string return a tuple `(String, Int)` from the closure. The integer value is the `month` component, then you are able to sort the array properly. – vadian Dec 24 '20 at 08:39
  • I try, bug get error: Declared closure result '(String, Int)' is incompatible with contextual type 'String' – Parcker Dec 24 '20 at 08:49
  • If you declare `item -> (String, Int) in` you have to return `return (date.monthAsString(), components.month!)` and vice versa. – vadian Dec 24 '20 at 09:15
  • I try, but anyway I get error: Declared closure result '(String, Int)' is incompatible with contextual type 'String' – Parcker Dec 24 '20 at 09:24
  • I added info to the question. In grouped = Dictionary(grouping: birthdaysList) – Parcker Dec 24 '20 at 09:27