Save key-value pair in .GlobalPreferences using Swift - swift

I'm writing a short Mac app (let's call it "myApp") and I need to read from and write to the .GlobalPreferences plist in ~/Library/Preferences/.
For the reading part, I use the following and it's working fine:
let boolValue = NSUserDefaults.standardUserDefaults().boolForKey("aKeyInGlobalPreferences")
However, I'm having some trouble in changing the value for that same key. I tried the following:
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "aKeyInGlobalPreferences")
NSUserDefaults.standardUserDefaults().synchronize()
Sadly, it didn't change the key value in .GlobalPreferences. Instead, it created a new plist com.myName.myApp with the key-value pair in there.
How can I make my app write to .GlobalPreferences instead of creating a new plist?

You need to write specifically into the global domain with setPersistentDomain().
NSUserDefaults.standardUserDefaults().setPersistentDomain(["aKeyInGlobalPreferences":true],
forName: NSGlobalDomain)

Related

How to read values from `defaults` in swift?

Target
I want to control the app's behavior through command line tool, like enabling some testing features. Then I thought maybe I can use defaults tool to set a configuration that app can read it.
The following is what I tried:
In command line
defaults write com.my.app enableFeature2 -bool true
In app
let userDefaults = UserDefaults.standard
userDefaults.addSuite("com.my.app")
let val = userDefaults.value(forKey: "enableFeature2")
print(val) // got a nil
Expected usage
let bEnableFeature2 = (val as? Bool) ?? false
if bEnableFeature2 {
enableFeature2();
}
Actual
The `val` from userDefaults is `nil`
In app's UserDefaults, it seems it unable to locate the plist and read a nil from it . I think I may do something wrong or miss something.
After the defaults command is issued, a plist file is generated at ~/Library/Preferences/ named com.my.app.plist, whose content is simply Root { enableFeature2/Boolean/YES }.
How should I bridge the interaction between command-line and app?
--
Others
My testing build is launched via XCode
Located in ~/Library/Developer/XCode/Derived/{APP_LONG_NAME}/
com.my.app is not my application domain, I am a module in application so I need an another domain name.

How to utilize default value in Root.plist for Swift app settings

I've set some default values for a swift app in the Root.plist file such as this:
My question is: how can I access the default value or utilize it? Once I load my app first time, the value is nil until I manually set it to something. As far as I can see, the only thing this does is visually give it that appearance in the settings app, but doesn't actually have a tangible value associated with it?
Side note: One thing I'd like to do if I have to manually set this is to do something like this:
UserDefaults.standard.set(DEFAULT_VALUE, forKey: "user_toggle_switch")
I'm not sure if there's any way to pull this information. Any clarification on how settings works with defaults values would be superb.
You are confusing the two. That plist is completely different from UserDefaults.
If you want to access the values from the plist, you need to access it
if let path = Bundle.main.path(forResource: "Root", ofType: "plist") {
if let rootDictionary = NSDictionary(contentsOfFile: path) {
// read here
}
}

Should we cache a CKServerChangeToken in CKDatabase and CKRecordZone separately?

In my app I use CloudKit and a user's private CKDatabase to store records. I do fetches for changes when the app starts as it adviced at WWDC 2016.
Firstly, I call the fetchDatabaseChanges(database: CKDatabase, databaseTokenKey: String, completion: #escaping () -> Void) method.
Inside this method in changesOperation.fetchDatabaseChangesCompletionBlock I save a CKServerChangeToken to userDefaults for a key : ckDatabaseToken.
I also call the fetchZoneChanges(database: database, databaseTokenKey: databaseTokenKey, zoneIDs: changedZoneIDs, completion in the changesOperation.fetchDatabaseChangesCompletionBlock of the fetchDatabaseChanges method.
In the fetchZoneChanges method there is an operation.recordZoneFetchCompletionBlock. Inside this block we also need to save the value of the token to the UserDefaults. And I'm saving it to another ckZoneToken variable in User Defaults. So inside the fetchZoneChanges I get and save (from/to UserDefaults) the ckZoneToken value, and inside the fetchDatabaseChanges, I get and save (from/to UserDefaults) the ckDatabaseToken value .
Is it the right technique? Or it is better to use only the one variable in both fetchDatabaseChanges and fetchZoneChanges method sto store the value of CKServerChangeToken?
Which will be the best approach?
Swift 3, Xcode 9
I've experimented with both ways and figured out that if we use the one changeToken in user defaults, we get a "Bad sync continuation data" error.
When I've used 2 separate values to store database changes and zone changes, I have had no errors.
So, I think that we have to cache a CKServerChangeToken both in CKDatabase and CKRecordZone separately.

What's wrong with my UserDefault code for global Array?

Trying to store an array through UserDefault, but Xcode gives me an error. The error message is Thread 1: Signal SIGABRT, and the console says "NSInvalidArgumentException, reason: Attempt to insert non-property list object". I have previously stored data in the array using this code:
let tempRecipe = GlobalFavorites(recipeImageObject: "", recipeTextObject: "", recipeHeaderObject: "", favoriteRecipeArray: [globalFavoriteRecipes])
tempRecipe.recipeHeaderObject = self.recipeClassArray[self.currentView].recipeHeaderObject
tempRecipe.recipeTextObject = self.recipeClassArray[self.currentView].recipeTextObject
tempRecipe.recipeImageObject = self.recipeClassArray[self.currentView].recipeImageObject
globalFavoriteRecipes.favoriteRecipeArray.append(tempRecipe)
And that works fine. Here's the code for storing with UserDefault that gives me the error:
UserDefaults.standard.setValue(globalFavoriteRecipes.favoriteRecipeArray, forKey: "savedFavoriteArray")
It's a global array and I want to store the whole array. I guess it has to do with how I write the array in UserDefault, because to me it seems that I'm trying to store something that's not there. Or what am I missing?
If globalFavoriteRecipes.favoriteRecipeArray is an array of custom objects, you need to make sure you are archiving and unarchiving them properly. Refer to this StackOverflow post.
That post also touches on some other options, as NSUserDefaults isn't the best place to store an array that can potentially grow pretty large, which it sounds like it could in this case based on the variable name.
Use set not setValue:
UserDefaults.standard.set(globalFavoriteRecipes.favoriteRecipeArray, forKey: "savedFavoriteArray")
But, I agree with #Jake, this should only be used to store small amounts of data, not a large array. Happy coding!

Saving to specific core data object in Today Extension

Working on a part of my iOS project that needs to refer to a specific object that the user selects in the main application that is set through a toggle switch that activates the specified object to be used in a Today Extension to record simple objects created by the user in the Today Extension. I am unsure how to go about doing this specifically. I thought about using NSUSerDefaults as the go to method for specifying that object but this is all entirely new to me. Has anyone gone down this path before on here? Does anyone know a way to refer to the specific object you want to store to in the Today Extension?
You can save a reference to a specific managed object by using the objectID property (an instance of NSManagedObjectID), which can be converted to a URI. The URI can then be converted to NSData, which you can save in user defaults.
To save the reference, get the object's ID as a URI:
let objectIDURI = newManagedObject.objectID.URIRepresentation()
Then convert to NSData and save:
let objectIDURIData = NSKeyedArchiver.archivedDataWithRootObject(objectIDURI)
NSUserDefaults.standardUserDefaults().setObject(objectIDURIData, forKey: "savedID")
To get the object back, load the NSData and convert back to an NSURL (the following is simplified, you'll have to handle optionals properly):
let savedObjectIDURIData = NSUserDefaults.standardUserDefaults().objectForKey("savedID") as? NSData
let savedObjectIDURI = NSKeyedUnarchiver.unarchiveObjectWithData(savedObjectIDURIData!) as? NSURL
Then convert the ID back to an NSManagedObjectID and get the managed object:
let savedObjectID = context.persistentStoreCoordinator?.managedObjectIDForURIRepresentation(savedObjectIDURI!)
let savedObject = context.objectWithID(savedObjectID!)
At this point savedObject is the managed object that was saved above.