How to sort birthdays in swiftUI so that next birthday from today is on top of the list?

I Have this struct

struct Friend: Identifiable {
  var name: String
  let bday: Date
  let id = UUID()
}

and this code:

struct FriendView: View {
   
  @Binding var friends: [Friend]
   
  var dateFormatter: DateFormatter { ... }
   
  var body: some View {
     
    VStack {     
        List {
          ForEach(friends) { friend in
            HStack {
              Text(friend.name)
              Spacer()
              Text(self.dateFormatter.string(from: friend.bday))
            }
          }
        }
    }
}

How to sort birthdays in swiftUI so that next birthday from today is on top of the list? I have tried putting friends.sorted(by: {$0.bday < $1.bday}) in if statement but then i just get the oldest friend on top, and not the next one who has a birthday this year. I would appreciate any help very much. Thank you in advance!

I have just created a playground to test this code out. It seems to work properly for the dates I have provided.

See what you think and whether it works in your project.

let calendar = Calendar.current

// Manual creation of dates so you can see what the values for each are

let components = [
    DateComponents(year: 2004, month: 3, day: 26),
    DateComponents(year: 1973, month: 7, day: 4),
    DateComponents(year: 1992, month: 4, day: 1),
    DateComponents(year: 2012, month: 12, day: 23),
    DateComponents(year: 1988, month: 9, day: 16)
]

let birthdays = components.compactMap(calendar.date)

// You would only need this bit in your project

let nextBirthdays = birthdays.compactMap {
    let components = calendar.dateComponents([.day, .month], from: $0)
    return calendar.nextDate(after: .now, matching: components, matchingPolicy: .nextTime)
}.sorted(by: <)

let nextBirthday = nextBirthdays.first
print(nextBirthday) // Optional(2022-12-23 00:00:00 +0000)


This part is what you would put in your project:

// Wrap up next birthday calculation inside a function
func nextBirthday(for birthday: Date) -> Date {
    let calendar = Calendar.current
    let components = calendar.dateComponents([.day, .month], from: birthday)
    return calendar.nextDate(after: .now, matching: components, matchingPolicy: .nextTime) ?? .distantFuture
}

// Sorted friends by next birthday first
friends.sorted {
    nextBirthday(for: $0.bday) < nextBirthday(for: $1.bday)
}

I understand you want to order by celebration day, havaing the first to celebrate at top…

I would try the following:

  • sort friends by birthdays by ascending order (only using month and day of course to sort as year is not relevant here)
  • filter into 2 arrays: birthBeforeToday and birthSinceToday, filtering by comparing month and day to todays month and day
  • rebuild a new global array: birthSinceToday + birthBeforeToday

Hope that's clear

I tested this (only had to replace binding by a direct declaration):

struct Friend: Identifiable {
  var name: String
  let bday: Date
  let id = UUID()
}

var friends : [Friend] {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy/MM/dd"

    let aFriend = Friend(name: "A", bday: formatter.date(from: "2004/03/26") ?? Date())
    let bFriend = Friend(name: "B", bday: formatter.date(from: "1973/07/04") ?? Date())
    let cFriend = Friend(name: "C", bday: formatter.date(from: "1992/04/01") ?? Date())
    let dFriend = Friend(name: "D", bday: formatter.date(from: "2004/10/08") ?? Date())
    let eFriend = Friend(name: "E", bday: formatter.date(from: "2012/12/23") ?? Date())
    let fFriend = Friend(name: "F", bday: formatter.date(from: "1988/09/16") ?? Date())
    let gFriend = Friend(name: "G", bday: formatter.date(from: "1955/03/25") ?? Date())
    return [
        aFriend,
        bFriend,
        cFriend,
        dFriend,
        eFriend,
        fFriend,
        gFriend,
    ]
}

func dayOf(_ date: Date) -> Int {
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "dd"
    let d = Int(dateFormatter.string(from: date)) ?? 0
    dateFormatter.dateFormat = "MM"
    let m = Int(dateFormatter.string(from: date)) ?? 0
    return 100 * m + d
}

func sortByCelebration(friends: [Friend]) -> [Friend] {
    let newList = friends.sorted(by: { dayOf($0.bday) < dayOf($1.bday) } )
    
    let before = newList.filter() { dayOf($0.bday) < dayOf(Date()) }
    let after = newList.filter() { dayOf($0.bday) >= dayOf(Date()) }

    return after + before
}

struct FriendView: View {
    
    /*@Binding var friends: [Friend] */
    private var ordered : [Friend] {
        sortByCelebration(friends: friends)
    }

    var body: some View {
        
        VStack {
            List {
                ForEach(ordered) { friend in
                    HStack {
                        Text(friend.name)
                        Spacer()
                        Text(friend.bday.formatted(.dateTime.day().month().year()))
                    }
                }
            }
        }
    }
}

And got this (french locale)

How to sort birthdays in swiftUI so that next birthday from today is on top of the list?
 
 
Q