I am trying to get the objects from realm, where newDate is later then firstDate. So if the date from firstDate is 05.10.2017, it will get objects after that date, example 06.10.2017, but not 04.10.2017.
This is how I am storing the date:
class User: Object {
#objc dynamic var firstDate = Date()
#objc dynamic var newDate = Date()
}
This is how I am saving the objects:
let date = Date()
let realm = try! Realm()
let myUser = User()
myUser.firstDate = self.date
This is how I am trying to retrieve the objects:
var userData: Results<User>?
if (homeIndexPathRow == 0) {
let getData = realm.objects(User.self).filter("firstDate > newDate")
userData = getData
print("userData", userData!)
}
When trying to retrieve the objects, the app crashes.. Is it something wrong with the filter format?
Try this:
var yourNSDate = NSDate()
let predicate = NSPredicate(format: "firstDate < %#", yourNSDate)
let dataResults = realm.objects(User.self).filter(predicate)
userData = dataResults
Replace that with the code below if (homeIndexPathRow == 0) { ...
Related
I have a custom object class as below which consists of 3 variables:
class DateObj {
var startDate: Date?
var endDate: Date?
var updatedEndDate: Date?
}
Below are the objects I created for it:
let obj1 = DateObj.init(startDate: 1/13/2022 7:00am, endDate: 1/13/2022 6:30pm, updatedEndDate: nil)
let obj2 = DateObj.init(startDate: 1/13/2022 10:30am, endDate: 1/14/2022 10:30am, updatedEndDate: 1/13/2022 10:30pm)
let obj3 = DateObj.init(startDate: 1/13/2022 11:30am, endDate: 1/14/2022 11:30am, updatedEndDate: 1/13/2022 7:30pm)
let obj4 = DateObj.init(startDate: 1/13/2022 1:30pm, endDate: 1/13/2022 5:30pm, updatedEndDate: nil)
Doesn't matter what the start date is, I want to compare values of endTime with updatedEndTime and want to sort such that the event that ends first (end time could be in endDate or updatedEndDate) should be first in the array and the event that ends last should be last in the array.
Note: updatedEndTime will always be less than endTime since the event could have ended earlier than expected time.
var inputDates = [obj1, obj2, obj3, obj4]
var expectedOutputDates = [obj4, obj1, obj3, obj2] // Expected output after sort
Code that I tried:
inputDates.sorted { (lhs, rhs) in
if let lhsUpdated = lhs.updatedEndDate, let rhsUpdated = rhs.updatedEndDate {
return lhsUpdated < rhsUpdated
} else if let lhsUpdated = lhs.updatedEndDate, let rhsEndTime = rhs.endDate {
return lhsUpdated < rhsEndTime
} else if let lhsEndTime = lhs.endDate, let rhsUpdated = rhs.updatedEndDate {
return lhsEndTime < rhsUpdated
} else if let lhsEndTime = lhs.endDate, let rhsEndTime = rhs.endDate {
return lhsEndTime < rhsEndTime
}
return false
}
My code is not giving me the expected output. Could someone help and let me know how to compare values from 2 different attributes for sorting an array?
Thanks!
So i have created a class for a Day and for a Drink. and I'm trying to track how much you drink in a day, but I'm struggling with saving multiple days. I'm currently managing to save the current day(with the amount drunk that day) but i don't know how to save more than one day.
I want to save an array of type Day with all the days. how can i do this?
This is my Day class:
public class Day: NSObject {
var date: Date
var goalAmount: Drink
var consumedAmount: Drink
func saveDay() {
let formatting = DateFormatter()
formatting.dateFormat = "EEEE - dd/mm/yy"
UserDefaults.standard.set(formatting.string(from: date), forKey: "date")
UserDefaults.standard.set(goalAmount.amountOfDrink, forKey: "goal")
UserDefaults.standard.set(consumedAmount.amountOfDrink, forKey: "consumed")
}
func loadDay() {
let rawDate = UserDefaults.standard.value(forKey: "date") as? String ?? ""
let formatter = DateFormatter()
formatter.dateFormat = "EEEE - dd/mm/yy"
date = formatter.date(from: rawDate)!
goalAmount.amountOfDrink = UserDefaults.standard.float(forKey: "goal")
consumedAmount.amountOfDrink = UserDefaults.standard.float(forKey: "consumed")
}
}
This is my Drink class:
class Drink: NSObject {
var typeOfDrink: String
var amountOfDrink: Float
}
i am calling saveDay() when there are any changes made to the day, and then loadDay() when the app opens.
A better approach would be is to store the object of the class in userDefaults instead of storing particular properties of that class. And use [Date] instead of Date to save multiple days
For this first, you have Serialize the object to store in userDefaults and Deserialize to fetch the data from userDefaults.
import Foundation
class Day: Codable {
var date = Date()
var goalAmount: Drink
var consumedAmount: Drink
init(date: Date, goalAmount: Drink,consumedAmount: Drink ) {
self.date = date
self.goalAmount = goalAmount
self.consumedAmount = consumedAmount
}
static func saveDay(_ day : [Day]) {
do {
let object = try JSONEncoder().encode(day)
UserDefaults.standard.set(object, forKey: "days")
} catch {
print(error)
}
}
static func loadDay() {
let decoder = JSONDecoder()
if let object = UserDefaults.standard.value(forKey: "days") as? Data {
do {
let days = try decoder.decode([Day].self, from: object)
for day in days {
print("Date - ", day.date)
print("Goal Amount - ", day.goalAmount)
print("Consumed Amount - ",day.consumedAmount)
print("----------------------------------------------")
}
} catch {
print(error)
}
} else {
print("unable to fetch the data from day key in user defaults")
}
}
}
class Drink: Codable {
var typeOfDrink: String
var amountOfDrink: Float
init(typeOfDrink: String,amountOfDrink: Float ) {
self.typeOfDrink = typeOfDrink
self.amountOfDrink = amountOfDrink
}
}
Use saveAndGet() method to store and fetch details from userDefaults
func saveAndGet() {
// use any formats to format the dates
let date = Date()
let goalAmount = Drink(typeOfDrink: "Water", amountOfDrink: 5.0)
let consumedAmount = Drink(typeOfDrink: "Water", amountOfDrink: 3.0)
let day1 = Day(date: date, goalAmount: goalAmount, consumedAmount: consumedAmount)
let day2 = Day(date: date, goalAmount: goalAmount, consumedAmount: consumedAmount)
let day3 = Day(date: date, goalAmount: goalAmount, consumedAmount: consumedAmount)
let day4 = Day(date: date, goalAmount: goalAmount, consumedAmount: consumedAmount)
let days = [day1, day2, day3, day4]
Day.saveDay(days)
Day.loadDay()
}
1) You need to create array of object for this :
goalAmount = [Drink]()
var date = [Date]()
and append with each new element.
you can also add date variable inside your drink class.
2) you can also create array of dictionary:
var userData = [String : Any]()
key will be you date and Any contain related to drink data in Any you can store Anything.
I'm trying to go through data and save it in my model, however whatever i do i keep getting the following error: Can't mutate a persisted array outside of a write transaction.. What am i doing wrong? i'm appending each match to the league however it does not seem to work?
realm model
class League: Object {
dynamic var id: Int = 0
dynamic var name: String? = ""
var matches = List<Match>()
override class func primaryKey() -> String {
return "id"
}
}
class Match: Object {
dynamic var matchId: Int = 0
dynamic var date: NSDate?
dynamic var homeName: String? = ""
dynamic var awayName: String? = ""
dynamic var awayScore: Int = 0
dynamic var homeScore: Int = 0
dynamic var leagueName: String? = ""
dynamic var homeLogo: NSData?
dynamic var awayLogo: NSData?
}
Code
for (_, item) in result {
if let leagueId = item["league"].int,
let leagueName = item["name"].string,
let allMatches = item["matches"].array {
let league = League()
league.name = leagueName
league.id = leagueId
for match in allMatches {
if let matchId = match["matchId"].int,
let tournament = match["tournament"].string,
let homeTeam = match["homeName"].string,
let awayTeam = match["awayName"].string,
let homeScore = match["homeScore"].int,
let awayScore = match["awayScore"].int,
let homeLogo = match["homeLogo"].string,
let awayLogo = match["awayLogo"].string,
let date = match["date"].string {
if let awayLogoUrl = NSURL(string: awayLogo),
let homeLogoUrl = NSURL(string: homeLogo) {
if let awayLogoData = NSData(contentsOfURL: awayLogoUrl),
let homeLogoData = NSData(contentsOfURL: homeLogoUrl) {
let matchObject = Match()
matchObject.matchId = matchId
matchObject.leagueName = tournament
matchObject.homeName = homeTeam
matchObject.awayName = awayTeam
matchObject.homeScore = homeScore
matchObject.awayScore = awayScore
matchObject.homeLogo = homeLogoData
matchObject.awayLogo = awayLogoData
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
formatter.timeZone = NSTimeZone(abbreviation: "CET")
matchObject.date = formatter.dateFromString(date)!
league.matches.append(matchObject)
}
}
}
print(league)
try! realm.write {
realm.add(league, update: true)
}
}
}
}
}
Simplifying your code to show only the general structure helps to reveal the issue:
let league = League()
league.name = leagueName
league.id = leagueId
for match in allMatches {
if … {
let matchObject = Match()
…
league.matches.append(matchObject)
}
print(league)
try! realm.write {
realm.add(league, update: true)
}
}
This is the sequence of events: You start by creating a standalone, unpersisted League instance, league. For each match, you create a standalone, unpersisted Match instance and append it to league.matches. You then create a write transaction, and save league to the Realm. From this point, league is no longer standalone, and may only be modified within a write transaction. On the next iteration of the loop you create another Match instance and attempt to append it to league.matches. This throws since league is persisted and we're not in a write transaction.
One solution here would be to restructure the code so you only save league to the Realm once, like so:
let league = League()
league.name = leagueName
league.id = leagueId
for match in allMatches {
if … {
let matchObject = Match()
…
league.matches.append(matchObject)
}
}
print(league)
try! realm.write {
realm.add(league, update: true)
}
The post above is wrong, you can't mutate a List of a realm model outside of a write block.
the correct way would be:
try! realm.write {
league.matches.append(matchObject)
realm.add(league, update: true)
}
At least this is the usual way with Realm Version 0.98
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 someone is fighting with Swift 3.0 syntax:
var lastDate = dateObjects.first?.start
let calendar = Calendar.current
var lastGroup = [DateObject]()
var days = [[DateObject]]()
for dateObject in dateObjects {
let currentDate = dateObject.start
let unitFlags : Set<Calendar.Component> = [.era, .day, .month, .year, .timeZone]
let difference = calendar.dateComponents(unitFlags, from: lastDate!, to: currentDate)
if difference.year! > 0 || difference.month! > 0 || difference.day! > 0 {
lastDate = currentDate
days.append(lastGroup)
lastGroup = [travelTime]
} else {
lastGroup.append(dateObject)
}
}
days.append(lastGroup)
How to reset some NSUserDefaults everyday at 00:00
Ex.
if Time = 00:00/New day
{
NSUserDefaults.standardUserDefaults().removeObjectForKey("Data")
}
I suggest you to wrap NSUserDefaults into a Dao: it will perform the logic you need retrieving the saved data or resetting the data.
1: Adding isToday to NSDate
First of all let's add a property to NSDate
extension NSDate {
var isToday: Bool {
let unitFlags : NSCalendarUnit = [.Year, .Month, .Day]
let componentsSelf = NSCalendar.currentCalendar().components(unitFlags, fromDate: self)
let componentsNow = NSCalendar.currentCalendar().components(unitFlags, fromDate: NSDate())
return (componentsSelf.year,componentsSelf.month, componentsSelf.day) == (componentsNow.year,componentsNow.month, componentsNow.day)
}
}
2: The Dao for NSUserDefaults
class UserDefaultsDao {
static let sharedInstance = UserDefaultsDao()
private var storage: NSUserDefaults { return NSUserDefaults.standardUserDefaults() }
var data: String? {
set {
storage.setObject(newValue, forKey: "data")
storage.setObject(NSDate(), forKey: "lastSet")
}
get {
guard let lastSet = storage.objectForKey("dastSet") as? NSDate where lastSet.isToday else {
storage.removeObjectForKey("data")
return nil
}
return storage.objectForKey("data") as? String
}
}
}
3: Usage
This is how you save your data
UserDefaultsDao.sharedInstance.data = "hello world"
And this is how you print it
print(UserDefaultsDao.sharedInstance.data)
Since NSUserDefaults does NOT work properly in Playground, you should test this on a real project.