Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS Realm grouping by date to tableView sections

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!

like image 263
John Avatar asked Sep 27 '15 12:09

John


3 Answers

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)
    }
like image 148
Ondrej Stocek Avatar answered Oct 23 '22 22:10

Ondrej Stocek


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.

like image 34
marius Avatar answered Oct 23 '22 23:10

marius


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.

like image 2
Milan Stevanovic Avatar answered Oct 24 '22 00:10

Milan Stevanovic