How to make group of User Defaults in swift? - ios

I want to store group of data into user defaults so that deleting that group deletes all the data in it. For example i want to store login related data in USER_DEFAULTS_LOGIN and all subscription related data in USER_DEFAULTS_SUBSCRIPTION. Now if I delete USER_DEFAULTS_LOGIN group it does not affects USER_DEFAULTS_SUBSCRIPTION group. This was possible in android preference as creation of preference requires name. I guess there should be similar solution in ios. I have heard of 'domains' and 'suits' but does not understands there purpose. Kindly help.

UserDefaults API provides a way to set data for a key. In your example, you would define your keys to be USER_DEFAULTS_LOGIN and USER_DEFAULTS_SUBSCRIPTION. The data you associate with those keys can be one of several types - bool, number, string, URL, date, array, dictionary, or a bag-of-bytes (NSData).
See https://developer.apple.com/reference/foundation/userdefaults for more details on types and API usage.

Related

Firebase: Maintain a Username Directory

I am creating a Social app and want to track if a username already exists or not. The username list is supposed to grow in future and the way I was doing it now was a key value pair of <string,bolean> like this:
name1: true,
name2: true
all the above data was to be stored in a single document and whenever I want to see if a user exists I would call this document and check accordingly. But here's the problem, firebase max document size is 1MBs and as the users grow this can be problematic, so wanted to know from firebase experts that what's the best way to solve this use case in firestore or realtime database but since I need to query exists maybe realtime db won't suit that well.
Note that I don't want any of firestore querying capabilities but only to check if an entry exists in the record or not and if not just add it.
The Realtime Database doesn't have a 1MB limit (since it has no concept of a document, and everything is just a tree of JSON), so I'd typically use that for the index of user names.
Checking whether a name exists is pretty simple there too, and in JavaScript would look something like:
const usernames = firebase.database().ref('usernames');
usernames.child('name1').once((snapshot) => {
if (snapshot.exists()) {
...
}
});

How to build structured realtime firebase database for iOS, swift?

I am currently setting up my realtime firebase database to my iOS application.
It is my first time trying to structure user data in a firebase database, and I am really, really struggling with understanding a few key things.
A bit of context to my application's database needs:
When a new user is created, the attributes assigned directly to the user are:
Age
Email
Username
Nationality
Later on, the user needs the option of creating personal diaries!
Each of these diaries being arrays/lists of objects... Where each object in a diary furthermore holds a few attributes in a list/array.
After reading everything I could find anywhere, I picture my database something like this:
I am terribly sorry if it becomes too specific - I will try to make the question as open as possible:
My question is:
How to create the different "children" programmatically and how to find the keys leading back to them, so I can refer to them at other times again? (when editing an attribute in a child).
A few methods I have tried:
setValue([ArrayOfObjects]) --> This creates the desired array, but I can't seem to find e.g. index 3 in this array, to allow user to change his/her email later on.
childByAutoID() --> This as well creates my array, but gives several other problems: User can only store one diary, still can't find the paths to specific indexes...
setValue(), andPriority() --> Can't seem to make the priority function. (Is this function also outdated??)
And a few more...
If anyone can tell me how to achieve just the first few steps in setting up my database structure, I will be forever grateful - I have spent literally all day on it and I am not moving forward ...
Or, at least tell me, if I am on the right track regarding my desired setup of the database. Is it flat enough? Is there a smarter way to store all these user created lists?
Thank you so much! :-)
I don't know Swift so my examples are in Dart but the methods are similar I believe.
First off, I would split the Users node into two. One to hold the user data, which is normally pretty static, and the other to hold the diaries. You would use the same uid key as reference to both. This way you have less nesting to worry about and therefore it is much easier to CRUD the data. If you are using Firebase to authenticate your users then I would use the unique key that Firebase creates for each user as the keys for these two nodes.
Then...
To create a user data node record the Dart code would be something like:
referenceUserData.child(<authenticated user id>).set({
"age": <age value>,
"email": <email value>,
"name": <name value>,
});
To create a user diary node object record the Dart code would be something like:
referenceUserData.child(<authenticated user id>).child(<diary key>).child(<diary object key>).set({
"object info value 1": <object value>,
"object info value 2": <object value>,
"object info value 3": <object value>,
});
You could also create all the object records at once by writing them as a List (array) using .set().
You also need to decide what your diary key should be. You could use Firebase to generate a unique key by using .push().set().
To read eg. the user data then your call could be:
referenceUserData
.child(<authenticated user id>)
.once()
.then(
(DataSnapshot snapshot) {
print(snapshot.key);
if (snapshot.value != null) {
print(snapshot.value);
<code to process your snapshot value>
}
};
BTW, 'priority' is legacy from the early days of Firebase RTDB so I wouldn't try to use it.

What are ways to store complex dynamic objects locally (iOS, swift)?

I have iOS app that takes data from the server as json and then serializes them into objects of different types. Types can be complicated, can contain subtypes, can inherit, so there is no any limitations. Another thing that makes everything even more complicated is some of types are stored as AnyObject? and only in run time they are being serialized into real types accordingly to the specific rules. Something like that:
class A {
var typeName: String?
var b: AnyObject?
}
Then when it's serialized it can be done something like that:
if let someClass = NSClassFromString(typeName) as? SomeGenericType.Type{
b = someClass.init()
}
Also querying should be done on all the data. Currently I'm trying to store all of them locally, then load into memory and query there from the code. I'm using User defaults, but they have some limitations, also I needed to provide custom coding to make it work, and each time when I add a new field it turned out that I missed something in coding and nothing works. So it's pain.
Ideally I would just do some magic command and all the objects are sent to local storage no matter how complicated they are. The same to extract them from this storage. Also, user change data so I can't just store primary Json. And I don't want to covert objects back to Jason as for it's pain too.
Any suggestions?
If you want to use sqlite then You can store whole object in one row! I means you can create table with 2 columns one is id and second is your dataobject(it's data type should be blob). Then convert your whole object into data. Then store in sqlite table and retrieve it as data then convert it to object when want to use. By this way your object will remains in same format as you asked
Firebase while meant for online synching and storage can also cache everything locally in case you are offline and perform query's against the local cache. It uses JSON.
CouchDB also has a mobile version for iOS.
Both of those are over kill if your dataset is small; you can just store it as a text file and read the JSON back in. See performance characteristics here. The graph is for a 7MB file so if you are significantly less than that your load time may be minimal.
NSKeyedArchiver.archivedData(withRootObject:) is great for storing custom objects as Data objects. The only thing you need to do to be able to use this is to make your custom objects conform to NSCoding. A great example can be found here:
Save custom objects into NSUserDefaults
Once you have the Data version of the object, it can easily be stored in UserDefaults, as a property in CoreData, or even in the app's keychain entries. Depending on your use case, sensitivity of data, and how much data you intend to store, you might want to use any number of storage methods. NSKeyedArchiver.archivedData(withRootObject:) allows you to pretty much use any of them.

How to fetch list of CKRecordIDs

I'd like to be able to download a list of all CKRecordIDs of a certain record type from a Cloud Kit database. I don't want the records themselves, since they have large assets that aren't needed until the user asks for them.
CKQuery seems to return CKRecords only. Any ideas?
For this you could use a CKQueryOperation and set the desiredKeys to the fields that you do want to return. I'm not sure if you can set it to an empty array. Since you only want the CKRecordID's and those are already in the meta data you would not need any fields. If it does not work, then just fetch one small field.

Is HKMetadataKeyWasUserEntered broken? I keep getting nil when there is data in the health app

I'm trying to grab data from the Health App. Specifically data that the user did not enter in themselves. For instance, I have an iPhone 6+ that logs the amount of steps that I take. There is also an option to add the data manually; If you add the data manually, the health app marks the data as "user added".
Here's what's confusing me. Let's say I added a step count of 22. When I query the data using HKStatisticsQuery with a predicate of
HKQuery.predicateForObjectsWithMetadataKey(HKMetadataKeyWasUserEntered, allowedValues: [true])
I get the correct result of 22 steps, since I set the allowedValues to true and that I added this myself. However, when I try to set allowedValues to false, I get no results
HKQuery.predicateForObjectsWithMetadataKey(HKMetadataKeyWasUserEntered, allowedValues: [false])
I do indeed have the step data in the health app, but it returned no results.
Check for the below possible areas to fix it:
did you authorized your application to access Steps data from HealthKit?
If your quering for steps count from your application without authorizing with HealthKit, then HealthKit will not return any exception it will simply returns the steps count which was entered from your application only if available, otherwise returns nil.
Before going to query health data, check for the authorization status for steps count using authorizationStatusForType: method available with HKHealthStore class.
Update 1:
My observations on wasUserEntered key is:
If user entering steps data from HEALTH app, respective HKQuantitySample stores metadata dictionary along with HKWasUserEntered key as TRUE automatically.
If user entering steps data from other than Apple's HEALTH app, respective health/fitness device or our application should send metadata dictionary with key HKWasUserEntered along with value as either TRUE/FALSE. Otherwise, the metadata property will contain nil object. Hence, Apple is not applying predicate(predicate contains metadata key) on the data which don't have metadata with it.
For debugging this metadata, try to print your HKQuantitySampleObject.metadata
Apple's implementation on metadata Vs NSPredicate:
If Health data observed from Health/fitness devices, HealthKit is not adding the metadata dictionary to the respective health record.
In case of Health applications other than Apple's Health app, the developer should manually add the metadata dictionary for his record of health data.
If there is no metadata for a specific health record and NSPredicate have a constraint on metadata then, HealthKit completely omitting to validate such records.
Finally,
It is advised to use
(instancetype)quantitySampleWithType:(HKQuantityType *)quantityType
quantity:(HKQuantity *)quantity
startDate:(NSDate *)startDate
endDate:(NSDate *)endDate
metadata:(NSDictionary *)metadata;
instead of
+ (instancetype)quantitySampleWithType:(HKQuantityType *)quantityType
quantity:(HKQuantity *)quantity
startDate:(NSDate *)startDate
endDate:(NSDate *)endDate;
to add metadata.
Reporting Apple regarding this bug that, predicate(which contains metadata key) should be applied on all the data irrespective of checking for metadata exists or not.
It's been a while and you have probably solved this by now, but I think it is worth posting here in case someone else comes across the same issue.
Like Bhanu mentioned, data created by Apple themselves doesn't seem to have the HKWasUserEntered entry as part of its metadata.
But Apple never claims to do it anyway in their docs, so the proper query would be one that filtered out items with HKWasUserEntered == true if that key was set, but also gave back everything else that had no metadata associated.
The ideia is that metadata is a Dictionary, and that type returns nil if a key does not exist. So there are three possible scenarios:
HKMetadataKeyWasUserEntered == nil
HKMetadataKeyWasUserEntered == false
HKMetadataKeyWasUserEntered == true
Out of those, you just want to filter out the last one, since that explicitly tells you that data was entered but the user.
This can be done using the + predicateForObjectsWithMetadataKey:operatorType:value: convenience method on HKQuery, like this:
HKQuery.predicateForObjects(withMetadataKey: HKMetadataKeyWasUserEntered, operatorType: .notEqualTo, value: true)
But of course, your initial question was: "Is HKMetadataKeyWasUserEntered broken?". And I don't believe it is, since, like mentioned, Apple never claims to write that key for their own values. :)

Resources