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.
Related
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.
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));
I want to be able to support copy and paste for a tableview row showing a core data entity. This entity has one attribute and two relationships. When I use the dictionary archiving technique recommended by Apple (from 'NSPersistentDocument Core Data Tutorial') I find that the relationships throw an error. Here's the essential piece of code where the problem occurs:
for (id sectionObject in selectedSectionsArray){
NSDictionary *thisDictionary = [sectionObject dictionaryRepresentation]; // 'sectionObject' has 1 attribute and 2 relationships (one-to-many)
[copyObjectsArray addObject:[sectionObject dictionaryRepresentation]];
}
NSPasteboard *generalPasteboard = [NSPasteboard generalPasteboard];
[generalPasteboard declareTypes:[NSArray arrayWithObjects:MSSectionsPBoardType, NSStringPboardType, nil] owner:self];
NSData *copyData = [NSKeyedArchiver archivedDataWithRootObject:copyObjectsArray]; // Here's where it crashes. ERROR MESSAGE: "-[NSManagedObject encodeWithCoder:] unrecognized selector sent to instance 0x22fd410"
Therefore, it seems the only way to copy a relationship to the pasteboard must be to archive its URI. In that case, I have to deal with the headache of referencing temporary ID's. Could someone please confirm that this is the case? Does it have to be so hard?
You haven't read that document closely enough. In the section Custom Employee Logic, it explains that relationships will not be copied for several reasons described there. It then explains how the code handles copying only specific attributes. It seems like you followed the document as far as choosing specific attributes to copy but not about leaving out relationships.
As for the error you're seeing,
-[NSManagedObject encodeWithCoder:] unrecognized selector sent to instance 0x22fd410
This happens because you're calling archivedDataWithRootObject: on a dictionary that contains objects that don't conform to NSCoding, specifically, your managed objects. Archiving like this only works automatically for property list types-- for everything else, you have to implement NSCoding, or you get this error.
Copying a managed object ID's URI is probably reasonable if you want to copy the relationships. If you're having problems with temporary object IDs, do one of the following:
Save changes
Call obtainPermanentIDsForObjects:error: for the objects to get permanent IDs without saving.
I've read through the KVC docs on Apple and it talks in depth about making your indexed collections accessible through key value coding, but I can't find any examples of a key path being used to access an arbitrary element within the array.
If my Blob class has an NSArray *widgets, I'd like to be able to get the widget at index 4 by doing something like:
[myBlob valueForKeyPath:#"widgets[4]"]
Is there anything like this?
myBlob answers to 'valueForKey:' and widgets being an NSArray answers to 'objectAtIndex:'.
So '[[myBlob valueForKey:#"widgets"] objectAtIndex:4]' should do the trick.
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.