2

Working in HealthKit, I have an array of HealthKit Workouts that I need to organize into month and year (so that I can display workouts from Jan 2018, Feb 2018 etc.). What makes it difficult in my mind is I first need to check if there is a workout for a given month and year, if not I need to create the array for it, if there is I need to append to the existing array. I also am unsure of the best data model, I was thinking of using a [[Month:Year]] but that doesn't seem very Swifty?

guard let workoutsUnwrapped = workouts else { return }

for workout in workoutsUnwrapped {
    let calendar = Calendar.current
    let year = calendar.component(.year, from: workout.startDate)
    let month = calendar.component(.month, from: workout.startDate)
}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
GarySabo
  • 5,806
  • 5
  • 49
  • 124
  • With things like this I don't really worry about "Swift", I'm more concerned with how best to sort the dates. And that means asking what you intend to do with the dates? Are you *primarily* working in chronological order? If so, do it by YYYYMMDD no matter the language. Are you looking to *primarily* work with "year-over-year"? You might (but maybe not) sort by MMYYYY or MMDDYYYY. Tell us (and yourself) that first then worry about how to make your code "Swift". (And do not be afraid to just go with what works. If those 5 mines of code - ignoring the final closing bracket - work for you, well? –  Feb 18 '18 at 17:30
  • Thank you what I’m trying to do is populate a tableview using a section to split up each Months workouts. – GarySabo Feb 18 '18 at 17:42
  • In addition to the answer supplied by @rmaddy, check this answer too: https://stackoverflow.com/questions/29578965/how-do-i-populate-two-sections-in-a-tableview-with-two-different-arrays-using-sw#29579335 –  Feb 18 '18 at 17:45

1 Answers1

6

I'd start by creating a struct to hold a year and month:

struct YearMonth: Comparable, Hashable {
    let year: Int
    let month: Int

    init(year: Int, month: Int) {
        self.year = year
        self.month = month
    }

    init(date: Date) {
        let comps = Calendar.current.dateComponents([.year, .month], from: date)
        self.year = comps.year!
        self.month = comps.month!
    }

    var hashValue: Int {
        return year * 12 + month
    }

    static func == (lhs: YearMonth, rhs: YearMonth) -> Bool {
        return lhs.year == rhs.year && lhs.month == rhs.month
    }

    static func < (lhs: YearMonth, rhs: YearMonth) -> Bool {
        if lhs.year != rhs.year {
            return lhs.year < rhs.year
        } else {
            return lhs.month < rhs.month
        }
    }
}

Now you can use this as a key in a dictionary where each value is an array of workouts.

var data = [YearMonth: [HKWorkout]]()

Now iterate your workouts:

guard let workouts = workouts else { return }

for workout in workouts {
    let yearMonth = YearMonth(date: workout.startDate)
    var yearMonthWorkouts = data[yearMonth, default: [HKWorkout]())
    yearMonthWorkouts.append(workout)
    data[yearMonth] = yearMonthWorkouts
}

Once this is done, all of your workouts are grouped by year/month.

You can build a sorted list of year/months for the keys in the dictionary.

let sorted = data.keys.sorted()

To apply this to a table view, use sorted to define the number of sections. For each section, get the array of workouts from data for the given YearMonth of the corresponding section.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • `return (lhs.year, lhs.month) < (rhs.year, rhs.month)` https://stackoverflow.com/a/48200611/2303865 – Leo Dabus Feb 18 '18 at 18:02
  • 2
    As of Swift 4 use init(grouping:by:) instead of the for loop. https://developer.apple.com/documentation/swift/dictionary/2919592-init – Josh Homann Feb 18 '18 at 23:59
  • I think it might be safer to use `Calendar(identifier: .gregorian)` instead of `Calendar.current`. Thank you otherwise for this elegant approach. The link @JoshHomann posted has died, this is (currently) a working copy: https://developer.apple.com/documentation/swift/dictionary/2995342-init – Moritz Jun 06 '18 at 07:39