One preferences for two applications - cocoa

I have two applications with id-s: com.myCompany.mayApp and com.myCompany.mayAppPro.
How can I use one pref file com.myCompany.mayApp.plist for two these applications?
Is it possible to use class NSUserDefaults for this?

Take a look at the following methods in NSUserDefaults:
- (NSDictionary *)persistentDomainForName:(NSString *)domainName;
- (void)setPersistentDomain:(NSDictionary *)domain forName:(NSString *)domainName;
- (void)removePersistentDomainForName:(NSString *)domainName;
They allow you read and write to a preferences file with a given domain name. An example is to read some common preferences for the Apple iApps:
NSUserDefaults* prefs = [ NSUserDefaults standardUserDefaults ];
NSDictionary* iAppsPrefs = [ prefs persistentDomainForName: #"com.apple.iApps" ];
NSArray* recentPaths = [ iAppsPrefs objectForKey: #"iTunesRecentDatabasePaths" ];
The previous code reads the array of recent paths for the iTunes database files.
The disadvantage of these methods are that they read and write the entire contents of the file. If the number items being stored is not really large then this is not generally a problem.

i think you'll have to use the CFPreference APIs to set shared values, but you can read the values using NSUserDefaults by adding the suite to the search path of the shared NSUserDefaults instance. of course, you may read the values using the CFPreference APIs too.
if your prefs are complex and you want to use cocoa bindings, you may as well write your own interface which wraps the keys/value/domain/host/user config.

Related

macOS command line app - User Defaults dictionaryRepresentation shows too many values

I a developing a macOS commandline application in Xcode, which uses User Defaults. I have the following code for my User Defaults
if let configDefaults = UserDefaults.init(suiteName: "com.tests.configuration") {
configDefaults.set("myStringValue", forKey: "stringKey")
configDefaults.synchronize()
print(configDefaults.dictionaryRepresentation())
}
This will create my own .plist file in the ~/Library/Preferences folder. If I look into the file, I can see only my single value which I added, which is perfectly fine. But when I call dictionaryRepresentation() on my UserDefaults object, the there are a lot of other attributes (I guess from the shared UserDefaults object), like
com.apple.trackpad.twoFingerFromRightEdgeSwipeGesture or AKLastEmailListRequestDateKey
Looking into the documentation of UserDefaults, it seems that this has to do with the search list of UserDefaults and that the standard object is in the search list:
func dictionaryRepresentation() -> [String : Any]
Returns a dictionary that contains a union of all key-value pairs in the domains in the search list.
There are also the methods addSuite and removeSuite for a UserDefaults object, so I am guessing I need to remove the .standard suite from my configDefaults object, but I don't know the name, which should be used for that in the method.
Is it possible to remove the .standard defaults from the dictionary representation? I basically just want all of my own data in a dictionary, nothing more.
The reason I am trying to get only my values from the UserDefaults, is that a have a number of object of a custom type Connection (which store the configuration to connect to a server), which are saved in the UserDefaults. On program start I want to be able to load all objects into my app. Therefore I thought I could use dictionaryRepresentation(), as it would return all elements in the UserDefaults. But then there should be only my Connection objects in the dictionary, so that I can cast it to [String: Connection].
Given your purpose (in your latest edit of your question), what you should do is store a collection of Connection objects under a single key. Then, look up that key to get the collection.
It's not clear if the collection should be an array of Connection objects or a dictionary mapping strings to Connections. That's a choice you can make.
But, in any case, you shouldn't rely on the defaults being empty of everything else.
In other words, you would do:
UserDefaults.standard.set(yourStringToConnectionDictionary, forKey:"yourSingleKey")
and later:
let connectionMap = UserDefaults.dictionary(forKey:"yourSingleKey")
then look up Connections in the connectionMap by their name/ID/whatever.
Though the other solution proposed by Ken Thomases may be better from a design standpoint, I've found a solution that does exactly what I initially wanted. Calling
UserDefaults.standard.persistentDomain(forName: "com.company.TestApp.configuration")
Returns a dictionary containing only the values I've added to the domain com.company.TestApp.configuration, using
let configs = UserDefaults.init(suiteName: "com.company.TestApp.configuration")!
configs.set(someData, forKey: someKey)
Strangely in the Apple documentation says this about persistentDomain(forName:):
Calling this method is equivalent to initializing a user defaults object with init(suiteName:) passing domainName and calling the dictionaryRepresentation() method on it.
But this is not the case (see my question). Clarification on that subject is more than welcome.

How to filter an NSArray of file urls by Spotlight File Metadata Attributes / NSMetadataQuery?

The NSMetadataQuery class seems to be how Finder/Spotlight searches for files via their metadata.
NSMetadataQuery class provided by the Foundation framework. Queries can be run in two modes: asynchronous, and asynchronous with live updates. The first simply performs the search on the files that exist at the time of the initial search. The latter continues to search. updating the data as the files that fulfill or no longer fulfill the search parameters update.
https://developer.apple.com/library/content/documentation/Carbon/Conceptual/SpotlightQuery/Concepts/Introduction.html#//apple_ref/doc/uid/TP40001843-BBCFBCAG
However, it seems oriented around providing a directory (searchScopes), and then asynchronously returning results that were found in those directories (NSMetadataQueryDidFinishGathering).
I already have an NSArray containing file urls. I would like to construct a filter/search of those NSURLs using the same metadata and query syntax as a Spotlight Search. But I will provide a list of files to quickly filer, rather than a provide a directory with and receive asynchronous results.
// Something like this...
let imageFileTypePredicate = NSPredicate(fromMetadataQueryString: "(kMDItemGroupId = 13)")
let imageURLs = allURLs.filter{ imageFileTypePredicate.evaluate(with:$0) };
However, that is using a standard NSPredicate search rather than a file metadata filter and is throwing the error:
this class is not key value coding-compliant for the key _kMDItemGroupId.
The Spotlight Metadata Attributes I'm interested in filtering by are listed here:
https://developer.apple.com/library/content/documentation/CoreServices/Reference/MetadataAttributesRef/Reference/CommonAttrs.html#//apple_ref/doc/uid/TP40001694-SW1
How can an array of file urls be filtered by Spotlight metadata?
Create an MDItem for each url to get the file's spotlight attributes.
MDItem is a CF-compliant object that represents a file and the metadata associated with the file.
https://developer.apple.com/reference/coreservices/1658213-mditem
MDItemRef item = MDItemCreateWithURL(kCFAllocatorDefault, url);
CFArrayRef attributes = MDItemCopyAttributeNames(item);
NSDictionary *attributeValues = CFBridgingRelease(MDItemCopyAttributes(item, attributes));

OS X - JavaScript for Automation - How to create and store variable for future use

I am creating a small program that will help automate the initialization of a virtual environment and startup of a Django server. I would like to share this program with others.
I am looking for a way to create a variable (location of a folder) through the use of an open file browser and store that variable so that the user does not have to enter it in the future.
How can I store new information in my program for future use? I investigated the use of the plist file but cant find any documentation anywhere. Thanks for your help!
You can persistently store simple values in the user's preferences by using the NSUserDefaults class from Cocoa.
The following script asks the user to choose a folder location the first time it is run and then stores the chosen location in the user's defaults. When the script is run again, it returns the stored location without prompting the user. (Change the suiteName and locationKey values as appropriate for your script.)
var currentApp = Application.currentApplication()
currentApp.includeStandardAdditions = true
var suiteName = "your.suite.name";
var locationKey = "your.prefs.key";
var $ud = $.NSUserDefaults.alloc.initWithSuiteName(suiteName);
var $location = $ud.stringForKey(locationKey);
var locationPath = $location.isNil() ? currentApp.chooseFolder() : Path($location.js);
$ud.setObjectForKey($(locationPath.toString()), locationKey);
locationPath
Note that you can also interact with the user's preferences using the defaults command-line tool, e.g.:
defaults read your.suite.name your.prefs.key
defaults delete your.suite.name your.prefs.key
I suggested using plist above, but I just ran across this great tool:
JSON Helper
It is a app that supports scripting. The examples are shown in AppleScript, but should be easily translated to JXA.
JSON Helper is an agent (or scriptable background application) which
allows you to do useful things with JSON (JavaScript Object Notation)
directly from AppleScript. JSON Helper has no interface, and runs in
the background waiting for AppleScripts [or JXA] to ask it to do something,
just like Apple's own System Events does.
JSON Helper contains a
number of commands that help you parse JSON into regular AppleScript
lists and records, and convert AppleScript list and records back into
valid JSON. In addition JSON Helper contains some convenience commands
to allow you to communicate easily with web based APIs.

Saving User's Coin Count

While my app is not a game, a coin-based game works as a good analogy.
In a game like this, the user has a certain number of coins that they can earn through gameplay or by purchasing an IAP. The main problem is securely storing how many coins the user has locally.
I know there are other questions like this, such as this. This answer basically says, there is no use in trying to save data securely; just deal with the fact that users can edit the data.
I would rather not just leave the data out in the open for users to change (if they can edit the data, why would they purchase an IAP?). I have looked into the Keychain, which has a public interface to view and edit the data, and NSUserDefaults, which saves to a Plist that can easily be changed.
Is there a better option for me, or should I just not even try (like the answer above suggests)?
Edit: The app is actually a developer tool that has a limited number of tokens. Because the users all have technical skills, the majority of them (I assume) have enough knowledge to find and change the Plist file or the Keychain data.
This solution worked for me very well. Try this: https://github.com/nielsmouthaan/SecureNSUserDefaults. It will store encrypted bool/string/float/integer in your UserDefaults file. Hopefully this is what you want. Make sure you download and add CocoaSecurity to your project. CocoaSecurity is a required element of SecureNSUSerDefaults, so you do not need to import it into any of your files. You must also download Base64, which is a required element of CocoaSecurity. You also need to add Base64 to your project, but do not need to import it into any of your files.
USAGE
Import the header file anywhere you want to use the encryption method.
#import <SecureNSUserDefaults/NSUserDefaults+SecureAdditions.h>
Then, set an encryption key, probably in your awakeFromNib method:
[[NSUserDefaults standardUserDefaults] setSecret:#"your_secret_goes_here"];
I recommend generating a random string of numbers and letters.
Then, you must store the information in your UserDefaults file.
[[NSUserDefaults standardUserDefaults]
setSecretObject:#"your_secret_object"
forKey:#"the_key_your_object_will be_stored_under"];
To retrieve the string, use this method:
NSString *retrievedData = [[NSUserDefaults standardUserDefaults]
secretStringForKey:#"the_key_your_object_will be_stored_under"];
I hope this helps!

NSUserDefaults per document

Does the AppKit framework provide a way to store NSUserDefaults per NSDocument? If not, how would you recommend to implement this?
If you have some key that identifies the document you can store an object (dictionary...) with that key in NSUserDefaults. Or, depending on how you define a "document" it could contain its own meta-data.
Use an NSDictionary. Store its contents along with the document. If the format you're saving into can't be modified to contain this dictionary, you might be able to switch to "document bundles", also called "document packages" -- directories containing both the data and a plist with the contents of the dictionary.
If you want to explicitly want to store per-document data inside the [NSUserDefaults standardUserDefaults]-provided facility, you may want to look into organizing things like this:
documentSpecificSettings - NSArray
item 0 - NSDictionary
url - NSData containing NSURL's bookmark
some_key - your custom key
another_key - your custom key
item 1 - NSDictionary
url - NSData containing NSURL's bookmark
some_key - your custom key
See documentation for NSURL, specifically +bookmarkDataWithContentsOfURL:error: and +URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error:. There are more methods around, but these are the ones you'll want to use.
As far as I understand it, you'll have to manually track whether or not the document still exists when resolving the bookmark into the URL. And I suspect you'll have to resolve each URL in the dictionary to see if it applies to your document.
This appears more complex than simply storing a dictionary along with the document.

Resources