Сортировать NSSortDescriptor по дате месяца. Swift - PullRequest
0 голосов
/ 16 июня 2020

Я показываю список дней рождения. Я хочу отсортировать их так, чтобы они начинались с января и заканчивались декабрем, а второй NSSortDescriptor был днем. Я хочу отобразить список, в котором первыми появляются ближайшие дни рождения.

В настоящее время сортирует по полной дате рождения. Таким образом, в настоящее время «6 февраля 2009 г.» отображается после «8 февраля 1997 г.», что я не хочу, поскольку год не имеет значения, когда я хочу знать, когда наступит следующий день рождения.

Как выглядит мой запрос FetchRequest:

    @FetchRequest(entity: Birthday.entity(), sortDescriptors: [
    NSSortDescriptor(keyPath: \Birthday.date, ascending: true),

]) var birthdays: FetchedResults<Birthday>

Как выглядит мой список:

                List {
                ForEach(birthdays, id: \.self) { birthday in
                    ZStack {
                        NavigationLink(destination: DetailView(birthday: birthday)) {
                            EmptyView()

                        }.hidden()

                        BirthdayView(name: birthday.name ?? "Unknown Name", birthDate: birthday.date ?? Date())

                    }
                }
                .onDelete(perform: deleteBirthdays)
            }

Я использую Core Data, и вот как выглядит мой объект:

enter image description here

Используя последние версии XCODE и Swift.

Заранее спасибо!

Я пробовал:

extension Date {
var isInToday: Bool { Calendar.current.isDateInToday(self) }
var nextBirthday: Date {
    isInToday ? self : Calendar.current.nextDate(after: Date(), matching: Calendar.current.dateComponents([.month, .day, .hour, .minute], from: self), matchingPolicy: .nextTime)!
}

}

extension Birthday {
var nextBirthday: Date { date!.nextBirthday }

}

@FetchRequest(entity: Birthday.entity(), sortDescriptors: [
NSSortDescriptor(keyPath: \Birthday.nextBirthday, ascending: true)
]) var birthdays: FetchedResults<Birthday>

Но при запуске приложения отображается следующая ошибка:

«Поток 1: Неустранимая ошибка: не удалось извлечь строку из KeyPath Swift.KeyPath»

Ответы [ 3 ]

0 голосов
/ 16 июня 2020

Возможное решение - это расширение NSDate (NSDate имеет решающее значение, поскольку Core Data широко основан на NSObject-KVO ).

Он вычисляет следующее вхождение даты независимо от года

extension NSDate {

    dynamic var nextOccurrence : NSDate {
        let calendar = Calendar.current
        let components = calendar.dateComponents([.month, .day], from: self as Date)
        return calendar.nextDate(after: Date(), matching: components, matchingPolicy: .nextTime)! as NSDate
    }
}

Соответствующий дескриптор сортировки:

NSSortDescriptor(key: "date.nextOccurrence", ascending: true)
0 голосов
/ 16 июня 2020

Вы можете создать собственную сортировку, чтобы отсортировать даты по входящему дню рождения и передать свою коллекцию, отсортированную в метод ForEach:

extension Collection where Element == Birthday {
    func sortedByIncomingBirthday() -> [Element] {
        sorted {
            // lhs date is today or is in the future
            ($0.date.month, $0.date.day) >= (Date().month, Date().day) ?
                // rhs date is today or is in the future
                ($1.date.month, $1.date.day) >= (Date().month, Date().day) ?
                    // sort them by a month day order
                    ($0.date.month, $0.date.day) < ($1.date.month, $1.date.day) :
                    // otherwise is in increasing order
                    true :
            // lhs date is in the past
            // sort them by a month day order
            ($0.date.month, $0.date.day) < ($1.date.month, $1.date.day)
        }
    }
}

ForEach(birthdays.sortedByIncomingBirthday(), id: \.self) { birthday in

Другой вариант - продлить день рождения и добавить свойство nextBirthday таким образом, вы можете отсортировать его, используя его в качестве keyPath для вашего NSSortDescriptor:

extension Date {
    var isInToday: Bool { Calendar.current.isDateInToday(self) }
    var nextBirthday: Date {
        isInToday ? self : Calendar.current.nextDate(after: Date(), matching: Calendar.current.dateComponents([.month, .day, .hour, .minute], from: self), matchingPolicy: .nextTime)!
    }
}

extension Birthday {
    var nextBirthday: Date { date.nextBirthday }
}

NSSortDescriptor(keyPath: \Birthday.nextBirthday, ascending: true)

edit / update:

если свойство «Дата рождения» является необязательным, не принудительно разворачивайте его значение:

extension Birthday {
    var nextBirthday: Date { date?.nextBirthday ?? .distantFuture }
}

, и первый подход также должен будет справиться с этим:

extension Collection where Element == Birthday {
    func sortedByIncomingBirthday() -> [Element] {
        sorted { lhs, rhs in
            if lhs.date == nil { return false }
            let lhs = lhs.date ?? .distantFuture
            let rhs = rhs.date ?? .distantFuture
            // lhs date is today or is in the future
            return (lhs.month, lhs.day) >= (Date().month, Date().day) ?
                       // rhs date is today or is in the future
                       (rhs.month, rhs.day) >= (Date().month, Date().day) ?
                           // sort them by a month day order
                           (lhs.month, lhs.day) < (rhs.month, rhs.day) :
                           // otherwise is in increasing order
                           true :
                   // lhs date is in the past
                   // sort them by a month day order
                   (lhs.month, lhs.day) < (rhs.month, rhs.day)
        }
    }
}
0 голосов
/ 16 июня 2020

Если у вас есть (или будет) большая база данных, то было бы целесообразно установить специальный атрибут в модели данных (например, BirthMonth: String, имеющий что-то вроде «MM / DD») и заставить механизм сортировки CoreData выполнять сортировку для вас по Birthday.birthMonth ключевому пути.

В противном случае, в случае небольшой базы данных, вы можете выполнить дополнительную операцию на лету после получения данных, как в

ForEach(birthdays.sorted { $0.month < $1.month }, id: \.self) { birthday in

, используя соответствующий .month расширение реализовано в соответствии с вашими потребностями.

...