Realm doesn't support DateInterval to be store into the database. For now our team do the following:
private let _intervalBegins = List<Date>()
private let _intervalEnds = List<Date>()
var dateIntervals: [DateInterval] {
get {
var intervals = [DateInterval]()
for (i, startDate) in _intervalBegins.enumerated() {
let endDate = _intervalEnds[i]
intervals.append(DateInterval(start: startDate, end: endDate))
}
return intervals
}
set {
_intervalBegins.removeAll()
_intervalBegins.append(objectsIn: newValue.compactMap{ $0.start })
_intervalEnds.removeAll()
_intervalEnds.append(objectsIn: newValue.compactMap{ $0.end })
}
}
Is there a more "proper" way to do this? Maybe to store both the start and end dates into one property/database column? And get those value directly without "parsing" them with another variable as we do now.
Thanks!
As you notice, Realm doesn't support DateInterval, but Realm is able to save your custom objects. In this case you can create your own RealmDateInterval (or so) and create initializer, that allows you to create object from DateInterval:
dynamic var start: Date = Date()
dynamic var end: Date = Date()
convenience init(dateInterval: DateInterval) {
self.init()
self.start = dateInterval.start
self.end = dateInterval.end
}
Next thing, when you retrieve RealmDateInterval from Realm you really want DateInterval instead. Here you can create a bridge function, that can convert RealmDateInterval to DateInterval or create a protocol with convert func and
adopt it to RealmDateInterval (i.e. clearly show everybody RealmDateInterval has specific functionality).
protocol DateIntervalConvertible {
func toDateInterval() -> DateInterval
}
extension RealmDateInterval: DateIntervalConvertible {
func toDateInterval() -> DateInterval {
return DateInterval(start: start, end: end)
}
}
Related
I'm trying to get multiple types of health data all with the same timeframe. My problem relies on the way that I should handle all the data that I get from those queries. Currently my model is as follows:
struct DailyData {
var steps: Int?
var distance: Int?
var calories: Int?
var exercise: Int?
}
class UserData {
let healthManager = HealthKitManager.shared
var dailyData = [DailyData]?
.
.
.
}
if I'm not mistaken, I can only query only one HKQuantityIdentifier at a time, so that means I need to call my getData() function from my HealthKitManager.swift once for every HKQuantityType that I have in my model:
func getData(type: HKQuantityTypeIdentifier, unit: HKUnit, days: Int, completed: #escaping (Result<[Int], Error>) -> Void) {
let calendar = NSCalendar.current
let interval = NSDateComponents()
interval.day = 1
let quantityType = HKQuantityType.quantityType(forIdentifier: type)!
var anchorComponents = calendar.dateComponents([.day, .month, .year], from: NSDate() as Date)
anchorComponents.hour = 0
let anchorDate = calendar.date(from: anchorComponents)
// Define 1-day intervals starting from 0:00
let query = HKStatisticsCollectionQuery(quantityType: quantityType,
quantitySamplePredicate: nil,
options: .cumulativeSum,
anchorDate: anchorDate!,
intervalComponents: interval as DateComponents)
query.initialResultsHandler = {query, results, error in
if let error = error {
completed(.failure(error))
return
}
let endDate = NSDate()
let startDate = calendar.date(byAdding: .day, value: -(days - 1), to: endDate as Date, wrappingComponents: false)
var completeDataArray: [Int] = []
if let myResults = results{
myResults.enumerateStatistics(from: startDate!, to: endDate as Date) { statistics, stop in
if let quantity = statistics.sumQuantity(){
let dayData = quantity.doubleValue(for: unit)
completeDataArray.append(Int(dayData))
}
}
}
completed(.success(completeDataArray))
}
healthStore.execute(query)
}
My problem is that I can't find a way to correctly set the received data into my model. Could someone point me in the right direction? I believe my model could be wrong, because as for what I have gather online, it's impossible to query multiple HKQuantityTypes in one query. Meaning that I would definitely have to set my model one [Int] of a HKtype at a time.
But with what I currently have, that would mean that when the first query returns I have to create multiple DailyData objects with almost all of the variables nil except the one I'm setting. Then, when the other queries return, I should do some array checking of dailyData matching the .count values with the one I got from the query, and that just feels wrong in my opinion. Not very elegant.
I tried another approach. Instead of having an array of a custom type, having a custom type that inside has an array of ints for every HKType that I need. But this has another problem: How could I "Keep in sync" the data from every HKType? having different arrays for every type would be, in my opinion, difficult to handle for example in a tableView. How could I set number of rows in section? Which type should I prefer? Also I would be accessing data over array indexes which may lead to bugs difficult to solve.
struct WeekData {
var steps: [Int]?
var distance: [Int]?
var calories: [Int]?
var exercise: [Int]?
}
class UserData {
let healthManager = HealthKitManager.shared
var weekData = WeekData()
.
.
.
}
This might be a weird question because I don't fully understand how transient and the new derived properties work in Core Data.
So imagine I have a RegularClass entity, which stores any class that repeats over time. A class can repeat for example every third day or every one week. This is how it looks in the data model:
(a RegularClass belongs to a Schedule entity, which in turn belongs to a Course entity)
Now, if our class repeats every third day, we store the number 3 in the frequency property, and a string "days" in unit property, which is then converted to an enum in Swift. A Schedule, which every RegularClass belongs to, has a startDate property.
To check if a class happens at a given date, I came up with nothing better than calculating the difference in specified unit between the startDate and the given date, then taking a remainder between the difference and frequency, and if it's 0, than it's the date in which a class can occur.
var differenceComponent: Calendar.Component {
switch unitType {
case .weeks:
return .weekOfYear
case .days:
return .day
}
}
func getDifferenceFromDateComponents(_ dateComponents: DateComponents) -> Int? {
switch unitType {
case .weeks:
return dateComponents.weekOfYear
case .days:
return dateComponents.day
}
}
func dateIsInActiveState(_ date: Date) -> Bool {
if let startDate = schedule?.startDate {
let comps = Calendar.current.dateComponents([differenceComponent], from: startDate, to: date)
if let difference = getDifferenceFromDateComponents(comps) {
let remainder = Int64(difference) % frequency // that is the key!
return remainder == 0
}
}
return false
}
func containsDate(_ date: Date) -> Bool {
if dateIsInActiveState(date) {
if unitType == .days {
return true
}
let weekday = Calendar.current.component(.weekday, from: date)
return (weekdays?.allObjects as? [Weekday])?.contains(where: { $0.number == weekday }) ?? false
}
return false
}
Now, the thing is that this code works perfectly for courses that I've already got from a fetch request. But is there a way to pass a date parameter in a NSPredicate to calculate this while request happens? Or do I have to fetch all the courses and then filter them out manually?
To solve this issue you could store your data as scalar types and then do simple arithmetic in your predicate. Rather than dates, use integers with a days-from-day-zero figure (or whatever minimum unit of time is necessary for these calculations). Store your repeat cycle as number-of-days.
Then you can use the calculation ((searchDate - startDate) mod repeatCycle) == 0 in your predicate to find matching classes.
As you have suggested, it might be sensible to denormalise your data for different search cases.
In my app I am we are sending TimeStamp along with parameters for API calls to get data from. I could use any random string to pass with the values but I am using TimeStamp because it will be different everytime so that I will get fresh data everytime without cashing. Now our requirement is to update TimeStamp every hour so that everytime when I make an API call instead of showing fresh data everytime, data will be updated every hour.
My API looks like this:
let url = "https://myurl.api.getthisapidata?id=\(someID)&nocache=\(timeStamp)"
Now in the place of TimeStamp I want to send some random string like "\(arc4random())". But I want this random string to be changed only after an hour so that I go to some other view or closes the app and opens it before an hour this random string should remain same.
I hope you understand what I am trying to convey.
TimeStamp extenstion:
extension Date {
var ticks: UInt64 {
let timeStamp = UInt64((self.timeIntervalSince1970 + 62_135_596_800) * 10_000_000)
return timeStamp
}
}
Usage:
print(Date().ticks)
Get current hour value from current date using component(_:from:) method. Then append a random string with it.
class RandomStringGenerator: NSObject {
static let shared = RandomStringGenerator()
var oldString:String {
get {
return UserDefaults.standard.string(forKey: "oldString") ?? ""
}
set {
UserDefaults.standard.set(newValue, forKey: "oldString")
}
}
func getString() -> String {
let random = Int64.random(in: 10_000_00..<10_000_000)
let hour = Calendar.current.component(.hour, from: Date())
if !oldString.hasPrefix("\(hour)") {
oldString = "\(hour)" + "\(random)"
}
return oldString
}
}
Update:
I needed to find the next notification request and the associated ID so I ended up going with this:
UNUserNotificationCenter.current().getPendingNotificationRequests {
(requests) in
var requestDates = [String:Date]()
for request in requests{
let requestId = request.identifier
let trigger = request.trigger as! UNCalendarNotificationTrigger
let triggerDate = trigger.nextTriggerDate()
requestDates[requestId] = triggerDate
}
let nextRequest = requestDates.min{ a, b in a.value < b.value }
print(String(describing: nextRequest))
}
I thought that this method might provide a more elegant solution but as Duncan pointed out below UNNotificationRequests are not comparable:
requests.min(by: (UNNotificationRequest, UNNotificationRequest) throws -> Bool>)
If anyone has a better solution then let me know.
I think that Sequence has a min() function for sequences of objects that conform to the Comparable protocol. I don't think UNNotificationRequest objects are comparable, so you can't use min() on an array of UNNotificationRequest objects directly.
You'd have to first use flatMap to convert your array of notifications to an array of non-nil trigger Dates:
UNUserNotificationCenter.current().getPendingNotificationRequests { requests in
//map the array of `UNNotificationRequest`s to their
//trigger Dates and discard any that don't have a trigger date
guard let firstTriggerDate = (requests.flatMap {
$0.trigger as? UNCalendarNotificationTrigger
.nextTriggerDate()
}).min() else { return }
print(firstTriggerDate)
}
(That code might need a little adjustment to make it compile, but it's the basic idea.)
I'm new to Swift and started my first app.
I'm trying to transfer data from the Apple watch to the iPhone using updateApplicationContext, but only get an error:
[WCSession updateApplicationContext:error:]_block_invoke failed due to WCErrorCodePayloadUnsupportedTypes
This is the code in my WatchKit Extension:
var transferData = [JumpData]()
func transferDataFunc() {
let applicationDict = ["data":self.transferData]
do {
try self.session?.updateApplicationContext(applicationDict)
} catch {
print("error")
}
}
These are the object structure I want to send:
class AltiLogObject {
let altitude : Float
let date : Date
init (altitude: Float) {
self.altitude = altitude
self.date = Date.init()
}
init (altitude: Float, date : Date) {
self.altitude = altitude
self.date = date
}
}
class JumpData {
let date : Date
let altitudes : [AltiLogObject]
init(altitudes: [AltiLogObject]) {
self.altitudes = altitudes
self.date = Date.init()
}
init(altitudes: [AltiLogObject], date : Date) {
self.date = date
self.altitudes = altitudes
}
}
To have it complete, the code of the receiver function:
private func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
let transferData = applicationContext["data"] as! [JumpData]
//Use this to update the UI instantaneously (otherwise, takes a little while)
DispatchQueue.main.async() {
self.jumpDatas += transferData
}
}
Any hints are welcome, as I'm trying to get it running for over a week now.
You can only send property list values through updateApplicationContext() - the documentation states this but it isn’t clear at all.
The type of objects that you can send can be found at https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/PropertyLists/AboutPropertyLists/AboutPropertyLists.html
Obviously your custom object types aren’t supported, so what you need to do is to break the properties down into the constituent parts (e.g your altitude and date properties), send them individually in the applicationContext dictionary passed into updateApplicationContext() and then reconstruct the objects on the receiving end.
It’s a real pain of course, but that’s the way this all works.
HTH