Transter custom object from Apple watch to iPhone using updateApplicationContext - swift

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

Related

Swift: ARKit TimeInterval to Date

The currentFrame (ARFrame) of ARSession has a timestamp attribute of type TimeInterval which represents the uptime at the moment the frame has been captured.
I need to convert this TimeInterval to the current time domain of the device.
If my assumption about timestamp is correct, adding the kernel BootTime and timestamp together would give me the correct date.
Problem: Adding the kernel BootTime and timestamp together gives me an Date that is not correct. (depending on the device`s last boot time up to 2 days variance)
Current Code:
func kernelBootTime() -> Date {
var mib = [CTL_KERN, KERN_BOOTTIME]
var bootTime = timeval()
var bootTimeSize = MemoryLayout<timeval>.stride
if sysctl(&mib, UInt32(mib.count), &bootTime, &bootTimeSize, nil, 0) != 0 {
fatalError("Could not get boot time, errno: \(errno)")
}
return Date(timeIntervalSince1970: Double(bootTime.tv_sec) + Double(bootTime.tv_usec) / 1_000_000.0)
}
public func checkTime(_ session: ARSession) {
guard let frame = session.currentFrame else { return }
print(Date(timeInterval: frame.timestamp, since: kernelBootTime()))
}
I found the solution.
var refDate = Date.now - ProcessInfo.processInfo.systemUptime
gives you the date of the last device restart
public func checkTime(_ session: ARSession) {
guard let frame = session.currentFrame else { return }
print(Date(timeInterval: frame.timestamp, since: refDate))
}
prints the exact time when the image was taken. (in UTC time)

Proper way to save a DateInterval to Realm

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)
}
}

What does .decode return in swift 4?

When you call .decode() to decode a struct, what exactly does it return?
I have look it up on the Apple Documentation, but all it says is "native format into in-memory representations." But what does this mean? Can anyone help me?
I'm asking this because my app is crashing when I get a null value from the JSON data, from this line of code:
let plantData = try decoder.decode([Plants].self, from: data)
Here is my struct:
struct Plants: Codable {
let date: String
let monthlyAVG: String?
enum CodingKeys : String, CodingKey {
case date = "Date"
case monthlyAVG = "30_Day_MA_MMBTU"
}
}
And Here is my Parsing code:
func parseJson() {
let url = URL(string: ebr_String)
// Load the URL
URLSession.shared.dataTask(with:url!, completionHandler: {(data, response, error) in
// If there are any errors don't try to parse it, show the error
guard let data = data, error == nil else { print(error!); return }
let decoder = JSONDecoder()
do{
let plantData = try decoder.decode([Plants].self, from: data)
print(plantData)
And Here is just a snippet of the information I am getting back:
MorrowTabbedApp.Plants(date: "2018-02-22", monthlyAVG: Optional("1210.06")), MorrowTabbedApp.Plants(date: "2018-02-23", monthlyAVG: nil)]
Here is the snippet of JSON from the web:
[
{"Date":"2018-02-21","30_Day_MA_MMBTU":"1210.06"},
{"Date":"2018-02-22","30_Day_MA_MMBTU":"1210.06"},
{"Date":"2018-02-23","30_Day_MA_MMBTU":null}
]
The decode method of JSONDecoder is a "generic" method. It returns an instance of whatever type you specified in the first parameter of the method. In your case, it returns a [Plants], i.e. a Array<Plants>, i.e. a Swift array of Plants instances.
If it's crashing because of a null value in your JSON, then you have to identify what was null, whether it was appropriate to be null, and if so, make sure that any Plants properties associated with values that might be null should be optionals.
Given your updated answer with code snippets, I'd suggest:
// Personally, I'd call this `Plant` as it appears to represent a single instance
struct Plant: Codable {
let date: String
let monthlyAVG: String? // Or you can use `String!` if you don't want to manually unwrap this every time you use it
enum CodingKeys : String, CodingKey {
case date = "Date"
case monthlyAVG = "30_Day_MA_MMBTU"
}
}
And:
do {
let plantData = try JSONDecoder().decode([Plant].self, from: data)
.filter { $0.monthlyAVG != nil }
print(plantData)
} catch let parseError {
print(parseError)
}
Note the filter line which selects only those occurrences for which monthlyAVG is not nil.
A couple of other suggestions:
Personally, if you could, I'd rather see the web service designed to only return the values you want (those with an actual monthlyAVG) and then change the monthlyAVG property to not be an optional. But that's up to you.
If monthlyAVG is really a numeric average, I'd change the web service to not return it as a string at all, but as a number without quotes. And then change the property of Plant to be Double or whatever.
You could, if you wanted, change the date property to be a Date and then use dateDecodingStrategy to convert the string to a Date:
struct Plant: Codable {
let date: Date
let monthlyAVG: String?
enum CodingKeys : String, CodingKey {
case date = "Date"
case monthlyAVG = "30_Day_MA_MMBTU"
}
}
and
do {
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd"
decoder.dateDecodingStrategy = .formatted(formatter)
let plantData = try decoder.decode([Plant].self, from: data)
.filter { $0.monthlyAVG != nil }
print(plantData)
} catch let parseError {
print(parseError)
}
You might do this if, for example, you wanted the x-axis of your chart to actually represent time rather than an evenly spaced set of data points.

How can I get the file creation date using URL resourceValues method in Swift 3?

Thanks to a variety of helpful posts in this forum, I have some code that works for obtaining the Creation Date of a single user-selected NSURL. However, I cannot get the code to work for either a hard-coded NSURL, nor within a loop through an NSFileManager enumerator.
I am not a professional programmer; I make apps that are tools for office. My ultimate goal is to simply sort an Array of NSURL objects based on Creation Date.
The code I am using is below, which functions just fine as is, but if I try to use the commented line to evaluate a specific PDF file, I get the following error:
I get the exact same error when I try to add this code to a loop of NSURL objects procured via the NSFileManager Enumerator.
I cannot figure out how to use the error instruction to solve the problem. If anyone can assist, that would be tremendous. Thank you.
let chosenURL = NSOpenPanel().selectFile
//let chosenURL = NSURL.fileURL(withPath: "/Users/craigsmith/Desktop/PDFRotator Introduction.pdf")
do
{
var cr:AnyObject?
try chosenURL?.getResourceValue(&cr, forKey: URLResourceKey.creationDateKey)
if (cr != nil)
{
if let createDate = cr as? NSDate
{
print("Seems to be a date: \(createDate)")
let theComparison = createDate.compare(NSDate() as Date)
print("Result of Comparison: \(theComparison)") // Useless
let interval = createDate.timeIntervalSinceNow
print("Interval: \(interval)")
if interval < (60*60*24*7*(-1))
{
print("More than a week ago")
}
else
{
print("Less than a week ago")
}
}
else
{
print("Not a Date")
}
}
}
catch
{
}
You can extend URL as follow:
extension URL {
/// The time at which the resource was created.
/// This key corresponds to an Date value, or nil if the volume doesn't support creation dates.
/// A resource’s creationDateKey value should be less than or equal to the resource’s contentModificationDateKey and contentAccessDateKey values. Otherwise, the file system may change the creationDateKey to the lesser of those values.
var creation: Date? {
get {
return (try? resourceValues(forKeys: [.creationDateKey]))?.creationDate
}
set {
var resourceValues = URLResourceValues()
resourceValues.creationDate = newValue
try? setResourceValues(resourceValues)
}
}
/// The time at which the resource was most recently modified.
/// This key corresponds to an Date value, or nil if the volume doesn't support modification dates.
var contentModification: Date? {
get {
return (try? resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate
}
set {
var resourceValues = URLResourceValues()
resourceValues.contentModificationDate = newValue
try? setResourceValues(resourceValues)
}
}
/// The time at which the resource was most recently accessed.
/// This key corresponds to an Date value, or nil if the volume doesn't support access dates.
/// When you set the contentAccessDateKey for a resource, also set contentModificationDateKey in the same call to the setResourceValues(_:) method. Otherwise, the file system may set the contentAccessDateKey value to the current contentModificationDateKey value.
var contentAccess: Date? {
get {
return (try? resourceValues(forKeys: [.contentAccessDateKey]))?.contentAccessDate
}
// Beginning in macOS 10.13, iOS 11, watchOS 4, tvOS 11, and later, contentAccessDateKey is read-write. Attempts to set a value for this file resource property on earlier systems are ignored.
set {
var resourceValues = URLResourceValues()
resourceValues.contentAccessDate = newValue
try? setResourceValues(resourceValues)
}
}
}
usage:
print(yourURL.creationDate)
According to the header doc of URL and URLResourceValues, you may need to write something like this:
(This code is assuming chosenURL is of type URL?.)
do {
if
let resValues = try chosenURL?.resourceValues(forKeys: [.creationDateKey]),
let createDate = resValues.creationDate
{
//Use createDate here...
}
} catch {
//...
}
(If your chosenURL is of type NSURL?, try this code.)
do {
if
let resValues = try (chosenURL as URL?)?.resourceValues(forKeys: [.creationDateKey]),
let createDate = resValues.creationDate
{
//Use createDate here...
print(createDate)
}
} catch {
//...
}
I recommend you to use URL rather than NSURL, as far as you can.
in swift 5 I use the following code:
let attributes = try! FileManager.default.attributesOfItem(atPath: item.path)
let creationDate = attributes[.creationDate] as! Date
sort array with the following code
fileArray = fileArray.sorted(by: {
$0.creationDate.compare($1.creationDate) == .orderedDescending
})
more about FileAttributeKey here: https://developer.apple.com/documentation/foundation/fileattributekey

Objects in Swift: Value of 'Object' has no member

Here's my doozy.
I've got this lovely little function in a file called functions.swift
//functions.swift
func latestActiveGoal() -> Object {
let realm = try! Realm()
let currentGoal = realm.objects(Goal).filter("Active == 1").sorted("CreatedOn").last
return currentGoal!
}
which returns a Goal object. (A Goal might be wanting to lose weight, or stop being so inept at Swift).
In a different view controller, I want to access this object. Here's what I'm trying:
//viewController.swift
#IBOutlet weak var aimText: UILabel!
let funky = functions()
func getGoals(){
var currentGoal = funky.latestActiveGoal()
print(currentGoal)
aimText.text = currentGoal.Title
}
The print(CurrentGoal) output shows this:
Goal {
id = 276;
Title = Goal Title;
Aim = Aim;
Action = Nothing;
Active = 1;
CreatedOn = 2016-02-12 00:14:45 +0000;
}
aimText.text = currentGoal.Title and aimText = currentGoal.Title both throw the error:
Value of 'Object' has no member 'Title'
By printing the contents of the object, I can see the data, but can't figure out how. Any help greatly appreciated.
As the error message said, currentGoal is a value of Object type which doesn't have member Title.
This is because function latestActiveGoal returns Object instead of Goal. You just need to make it return Goal by change the return type:
func latestActiveGoal() -> Goal {
Just replace your functions with below code.
It will works perfect.
This fuction will check if goal available, then only it will return.
func latestActiveGoal() -> Object? {
let realm = try! Realm()
let currentGoals = realm.objects(Goal).filter("Active == 1").sorted("CreatedOn")
if currentGoals.count > 0 {
return currentGoals.last;
}
return nil;
}
Your getGoals method will be as follow.
func getGoals(){
if let currentGoalObject = funky.latestActiveGoal() {
print(currentGoalObject)
let goal = currentGoalObject as! Goal
print(goal.Title)
aimText.text = goal.Title
}
}