I have realm database, which contains data and date of adding this data. I want to exctract this and set date as table view section header and data as rows data for each section depend on date. I know how to exctract but dont know how to group by date and set data for each section depend on date. Thank you!
Swift 4 implementation using higher order functions rather then loops.
class Item: Object {
@objc dynamic var id: Int = 0
@objc dynamic var date: Date = Date()
}
let realm = try! Realm()
// fetch all Items sorted by date
let results = realm.objects(Item.self).sorted(byKeyPath: "date", ascending: false)
let sections = results
.map { item in
// get start of a day
return Calendar.current.startOfDay(for: item.date)
}
.reduce([]) { dates, date in
// unique sorted array of dates
return dates.last == date ? dates : dates + [date]
}
.compactMap { startDate -> (date: Date, items: Results<Item>) in
// create the end of current day
let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate)!
// filter sorted results by a predicate matching current day
let items = results.filter("(date >= %@) AND (date < %@)", startDate, endDate)
// return a section only if current day is non-empty
return items.isEmpty ? nil : (date: startDate, items: items)
}
You can just sort your retrieved Results
by date and then split them up while iterate through to make them accessible in a grouped / hierarchic manner.
class Person {
dynamic var name = ""
dynamic var date = NSDate()
}
let sortedObjects = realm.objects(Person).sorted("date")
var lastDate = objects.first?.date
let calendar = NSCalendar.currentCalendar()
var lastGroup = [Person]()
var groups = [[Person]]()
for element in sortedObjects {
let currentDate = element.date
let difference = calendar.components([.Year, .Month, .Day], fromDate: lastDate!, toDate: currentDate, options: [])
if difference.year > 0 || difference.month > 0 || difference.day > 0 {
lastDate = currentDate
groups.append(lastGroup)
lastGroup = [element]
} else {
lastGroup.append(element)
}
}
groups.append(lastGroup)
Note: In that way, you would need to keep all your elements in memory. If that shouldn't work out for you, depending on your use-case, you could memorize only the indexes instead, which you can use to access the element from the retrieved Results
.
I had the exact same issue, I needed to display one kind of Realm entities in a sectioned table, grouped by date, and this is how I did it.
Example class containing the date field:
final class Appointment: Object {
@objc dynamic var id: Int = 0
@objc dynamic var date: Date?
}
Example code that will get all objects and split them in sections/results, grouped by unique date:
// (un)safely get an instance of Realm
let realm = try! Realm()
// get all the dates
// note that begginingOfDay is a extension on Date
// which gives back the beggining of the day of the given Date as a Date
// we are doing this in order to filter out non-unique dates later
let dates = self.realm.objects(Appointment.self).toArray().flatMap({ $0.date ?? nil }).map({ $0.beginningOfDay() })
// cast it to a Set to make values unique, and back to an Array for further use
let uniqueDates = Array(Set(dates))
let predicates = uniqueDates.map({ date -> NSPredicate in
// in order to use Swift's Date with NSPredicate
// it must be casted to NSDate
let begginingOfDay = date.beginningOfDay() as NSDate
let endOfDay = date.endOfDay() as NSDate
// create a predicate that checks if the given Date is in between
// the beggining of a given Date and the end of the given Date
let predicate = NSPredicate(format: "(date >= %@) AND (date <= %@)", begginingOfDay, endOfDay)
return predicate
})
// create an array of Results<Appointment>, and then use it to drive your table/collection view
// I will leave this part to you, depends on your UI implementation
// personally, I wrap this into another object that contains results, section index, section title, etc.
// and then I use all of that in my table view's data source methods
let sectionedResults: [Results<Appointment>] = predicates.map({ predicate -> Results<Appointment> in
let results = realm.objects(Appointment.self).filter(predicate)
return results
})
You should now have a rough idea how to do it know, I'll leave the details of the UI implementation to you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With