How to query for samples from health kit that have an HKDevice - ios

I want to query samples from HealthKit but in order to prevent inaccurate or manipulated data I don't want samples that were written to health by other apps. Does anyone have any idea what predicate I can use to filter out data from all apps or to only allow data from devices? Thanks in advance.
Edit: I've realized that apps can save data to health with an HKDevice included. So filtering out samples that don't have devices won't work.

If what you want to do is exclude manually entered data, see this answer: Ignore manual entries from Apple Health app as Data Source
Samples that were added to HealthKit by the user via Health will have the HKMetadataKeyWasUserEntered key.

You can filter out the results that are not stored by apple in your query instead of iterating all the results.
first, you need to get all the sources for your desired type.
let query = HKSourceQuery(sampleType: type,
samplePredicate: predicate) {
query, sources, error in
// error handling ...
// create a list of your desired sources
let desiredSources = sources?.filter {
!$0.bundleIdentifier.starts(with: "com.apple.health")
}
// now use that list as a predicate for your query
let sourcePredicate = HKQuery.predicateForObjects(from: desiredSources!)
// use this predicate to query for data
}
you can also combine other predicates using NSCompoundPredicate

I'm still open to suggestions and alternate solutions but here is my work-around since I was unable to figure out how to use a predicate to get the job done.
let datePredicate = HKQuery.predicateForSamples(withStart:Date(), end: nil, options: [])
let sampleQuery = HKAnchoredObjectQuery(type: sampleType,
predicate: predicate,
anchor: nil,
limit: Int(HKObjectQueryNoLimit)) { query,
samples,
deletedObjects,
anchor,
error in
if let error = error {
print("Error performing sample query: \(error.localizedDescription)")
return
}
guard let samples = samples as? [HKQuantitySample] else { return }
// this line filters out all samples that do not have a device
let samplesFromDevices = samples.filter {
$0.device != nil && $0.sourceRevision.source.bundleIdentifier.hasPrefix("com.apple.health")
}
doStuffWithMySamples(samplesFromDevices)
}
As you can see, I just filter the data once it comes through rather than doing it before-hand.
Edit: Seems like the sources listed in health are separated into apps and actual devices. Not 100% sure how they do this but it seems like the sources under the device section all have a bundle identifier prefixed with com.apple.health. Hopefully this works.

Related

Firestore in Swift - Listener producing needless reads

I have an app that uses a snapshot listener to listen to data in a particular document. However, when a field in the document is updated, the data is read 7-10x over. Never read once, and never read the number of fields that are in my document, it always seems to be an arbitrary number. Also, when the read data prints out, it seems like every printout is the same except for a couple of fields that I'm not setting (like an array prints out "<__NSArrayM 0x282d9f240>" but the number changes on each print). As a result, minimal usage of my app is causing 5-10k reads. I'm trying to reduce the number of reads and I don't know exactly how, but the app has to read as data is updated, but my two questions are:
when I print the data from the listener, does each data print out signify a separate read operation? and
is there any way for the listener to be alerted of the update but wait to actually perform the read until the data is updated, then perform one read instead of multiple reads every time any field is updated? Or another strategy to reduce reads when multiple writes occur?
Not sure if this is helpful, but here is the code I'm using to perform the read...its pretty much the standard code from the firestore sdk:
env.db.collection(env.currentSessionCode!).document(K.FStore.docName).addSnapshotListener { [self] documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching snapshot: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
self.env.data1 = data[K.FStore.data1] as? String ?? "????"
self.env.data2 = data[K.FStore.data2] as? String ?? "????"
self.env.data3 = data[K.FStore.data3] as? [String] ?? ["????"]
self.env.data4 = data[K.FStore.data4] as? [String] ?? ["????"]
self.env.data5 = data[K.FStore.data5] as? Double ?? 0
self.env.data6 = data[K.FStore.data6] as? Double ?? 0
self.env.data7 = data[K.FStore.data7] as! Bool
self.env.data8 = data[K.FStore.data8] as! Bool
print("Current data: \(data)")
Update - For clarification, the way I have been updating my data to firebase is with a environment object, and using "didSet" when the new data is changed/updated in the environment to update it on firebase...I think this might be the root of the problem, as the function called on didSet runs 4-5 times each time it is called...
relevant code:
#Published var data1: String {
didSet {
postValuesToFB(fb: K.FStore.data1, string: data1)
}
}
func postValuesToFB(fb: String, string: String) {
guard let code = currentSessionCode else {
fatalError("Error - Connection Check - no value for current session code in Global Env")
}
let docRef = db.collection(code).document(K.FStore.docName)
docRef.getDocument { document, _ in
guard let document = document else {
return
}
if document.exists {
let session = self.db.collection(code).document(K.FStore.docName)
session.updateData([
fb: string,
K.FStore.dateLastAccessed: FieldValue.serverTimestamp(),
])
return
}
}
}
Based on your comments, it sounds as if you've written no code to remove a listener after it's been added. Based on this, it's relatively safe to assume that your code could be adding many listeners over time, and each one is getting called for each change.
You should take a moment to think about the architecture of your app and figure out when is the appropriate time to remove listeners when they're no longer needed. Usually this corresponds with the lifecycle of whatever component is responsible for display of the data from the query. Review the documentation for getting realtime updates, especially the section on detaching a listener. It's up to you to determine the right time to remove your listener, but you definitely don't want to "leak" a listener as you are now.
A common source of unexpected read charges for developers who are new to Firestore is the Firebase console itself. When that console displays Firestore content, you are charged for those read too. To ensure you measure the impact of your code correctly, test it with the Firebase console closed.
when I print the data from the listener, does each data print out signify a separate read operation?
Not really. You get charged for a document read, when the document is read on your behalf on the server. You are not charted for printing the same DocumentSnapshot multiple times.
is there any way for the listener to be alerted of the update but wait to actually perform the read until the data is updated
Nope. To know the document has changed, the server needs to read it. So that requires a charged read operation.

Sequence number from iCloud/CloudKit

I want to get a sequential invoice number from iCloud using CloudKit, Swift 4.2+ and iOS 11+.
I have never done this with iCloud. Not sure what is the best way to go.
This number will be written into a PDF document which will be saved back to the public iCloud database.
Predicates are limited. I don't think I can use max(number). Obviously no SQL options.
I'm thinking about using notifications to update each device when a number is used and store that locally. But can I trust that? Or do I create another server just to get a unique number?
Not sure if I can use a query with a limit of 1 and sorted by date and then prefill the records with potential numbers (see code below) or just have one record which is updated each time?
func getNextInvoiceNumber() {
let predicate = NSPredicate(format: "status == %#", "unused")
let query = CKQuery(recordType: "Sequence", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "sequenceNumber", ascending: true)]
let operation = CKQueryOperation(query: query)
operation.resultsLimit = 1
operation.recordFetchedBlock = { (record) in
DispatchQueue.main.async {
let invoiceNumber = record["sequenceNumber"]
// Update the Record so that it is not used again using CKModifyRecordsOperation
record["status"] = "used" // do I also set recordName because it must be unique?
// save this record back to iCloud so it can't be used again
// if I can save it successfully I can use it
self.saveUsedInvoiceNumber(record: record) // will need to retry this in case someone got this number before my save
// Write a valid invoiceNumber to the PDF
self.addInvoiceNumberToDocument(number: invoiceNumber as! Double) // prob move to completion handler from above
}
}
CKContainer.default().publicCloudDatabase.add(operation)
}
func saveUsedInvoiceNumber(record: CKRecord) {
// CKModifyRecordsOperation(...)
//
}
func addInvoiceNumberToDocument(number: Double) {
// write invoice Number to the PDF file and save to disk
// save PDF file to iCloud
}
I am mindful of the potential for conflict with multiple transactions and concurrent users.
Any suggestions on ways you've handled this in iCloud?
Here, way you suggested will work if not offline requirement. You should use one record instead of array. Array will increase size day by day and may be create issue in fetch time.
Every record in iCloud has a unique ID. Why not use that as your invoice number, it shouldn't change unless you delete and recreate your record. You get that for free.

Slow data return Parse iOS swift 3

I am using Parse version "1.14.4" iOS 10.3.2 and swift 3.
The query is slow whether local (he objects returned are pinned) or remote.
Thanks
let placeObject = PFObject(className:"PlaceObject")
let point = PFGeoPoint(latitude:self.PointGlobal.latitude, longitude:self.PointGlobal.longitude)
placeObject["location"] = point
let query = PFQuery(className:"CLocationObject")
// Interested in locations near user.
query.whereKey("location", nearGeoPoint:point)
// Limit what could be a lot of points.
query.limit = 200
let localQuery = (query.copy() as! PFQuery).fromLocalDatastore()
localQuery.findObjectsInBackground{
(objects: [PFObject]?, error: Error?) -> Void in
self.dataReturnedLocally = true
.....
if self.dataReturnedLocally{
print("local query with no error there was data already")
}
else {
print("getting data remotely")
query.findObjectsInBackground{
(objects: [PFObject]?, error: Error?) -> Void in
if error == nil {
if let objects = objects {
geo based queries are the slowest types of queries with MongoDB, unfortunately. Also, there are not automatically indexes based on location, making these extra slow, especially for large collections. So, your only real solution is to add indexes to your database to index the location, optimized for the location queries you'll need to make. Though keep in mind too many of these affects write speed.
Depending on your use case, it may be better to use withinMiles instead of nearGeoPoint. This will return fewer results, but will not take as long to run, either.
All queries in the LDS are slow at the moment as they are not indexed. The LDS stored an objectId / JSON representation of the data and all filtering is done in memory.

Swift Parse how to make app works offline?

I'm working on local parse with swift 3.0
I'm doing querys to get results from server. but if there's no connection it wont show last results we got because losing connection.
so what i want to do is to save query results to view it if there is no connection
this is the query:
var query = PFUser.query()
query = PFQuery(className: "_User")
// query?.fromLocalDatastore()
query!.whereKey("objectId", equalTo: PFUser.current()!.objectId!)
query!.findObjectsInBackground {
(objects , error) -> Void in
if error == nil {
for object in objects! {
self.usernamelbl.text = object["username"] as! String
if let userp = PFUser.current()?["photo"] as? PFFile {
userp.getDataInBackground {
(imageData, error) -> Void in
if error == nil {
self.profilepic.image = UIImage(data: imageData!)!
}
}
}
}
Now how can i save the results and view them offline also if app closed?
Any help will be appreciated
It's possible to set the caching policy of specific PFQuery calls. To save a copy to disk, and rely on that before making another network hit, you set the kPFCachePolicyCacheElseNetwork policy.
However according to this Parse question, there is apparently a pretty strict limit on the size these caches are allowed to be. I'm not sure if those still apply in the open source version of Parse, but if you want to save more information to disk, it might be appropriate to use a more dedicated data persistence framework, like Core Data, SQLite, or Realm (Full disclosure: I work for Realm. :) )
For the purposes of image files, I'd recommend you manually manage the caching of those on disk, instead of storing it in Parse's cache (Due to the size constraints). There are some great image caching libraries out there (Like PINCache) that make it very easy.

iOS 9 CloudKit: query does not return anything while connected to cellular network

I'm developing an app with xcode 7 beta 5. When i run my app on my iphone 6 and i try to connect it to CloudKit if my iphone works in wifi mode all it's ok, i display all my data; but if my iphone works in LTE mode i can't see any kind of data. Does anyone know how to do this?
func getRecordsFromCloud() {
lavori = []
/
let _cloudContainer = CKContainer.defaultContainer()
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
/
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Lavori", predicate: predicate)
/
let queryOperation = CKQueryOperation(query: query)
queryOperation.desiredKeys = ["image","name"]
queryOperation.queuePriority = .VeryHigh
queryOperation.resultsLimit = 50
queryOperation.recordFetchedBlock = { (record:CKRecord) -> Void in
let lavoriRecord = record
self.lavori.append(lavoriRecord)
}
queryOperation.queryCompletionBlock = { (cursor:CKQueryCursor?, error:NSError?) -> Void in
if (error != nil) {
print("Failed to get data from iCloud - \(error!.localizedDescription)")
}
else {
print("Successfully retrieve the data from iCloud")
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
}
/
publicDatabase.addOperation(queryOperation)
}
Thanks, Alessio
Open the settings app, find your app, enable 'use mobile data'
Update: As discussed below
Adding the following line of code solved the problem:
queryOperation.qualityOfService = .UserInteractive
The reason why this works must be a timing / load issue. My initial guess would be that this is caused by this line:
queryOperation.queuePriority = .VeryHigh
The documentations states for the .queuePriority this:
You should use priority values only as needed to classify the relative priority of non-dependent operations.
The documentation states for the .qualityOfService this:
The default value of this property is NSOperationQualityOfServiceBackground and you should leave that value in place whenever possible.
So please try removing both the .queuePriority and .qualityOfService and see if it's working.
Update 2: Apparently this is a CloudKit bug. More people have the same issue. Please report it at https://bugreport.apple.com
I resolved this in the end by turning on the iCloud Drive data setting. This is actually quite hard to find. I'm on ios 14.1. My path to the setting was:
Settings -> Mobile Data - (then right at the bottom of Mobile Data) -> iCloud Drive.
I toggled this on and then everything works.

Resources