I have an IOS5 project.
I have added a settings bundle to my project and added some settings to it.
I set properties of 'host_ip':
<dict>
<key>AutocapitalizationType</key>
<string>None</string>
<key>AutocorrectionType</key>
<string>No</string>
<key>DefaultValue</key>
<string>http://localhost</string>
<key>IsSecure</key>
<false/>
<key>Key</key>
<string>host_ip</string>
<key>KeyboardType</key>
<string>URL</string>
<key>Title</key>
<string>Host</string>
<key>Type</key>
<string>PSTextFieldSpecifier</string>
</dict>
I try to read value like this:
NSUserDefaults *userDefaults =[NSUserDefaults standardUserDefaults];
NSString *host = [userDefaults stringForKey:#"host_ip"];
NSLog(#"%#",host);
It does not return the default value I set in host_ip, it returns nil.
How can I get my default value?
UPDATE
After a user edits a setting, I can retrieve it by the code above. My problem is getting the setting value, if it was not edited by the user yet. As I think in that case it should return the default I set in plist.
Did you register the defaults via NSUserDefaults?
See the registerDefaults: method on NSUserDefaults.
Related
I'm trying to add a Finder service and all looks fine until I want the service to do its job.
This is the method in my AppDelegate.m:
-(void)uploadFromPasteboard:(NSPasteboard *)pboard userData:(NSString *)udata error:(NSString **)err
{
NSString *filename = [pboard stringForType:NSURLPboardType];
dbg(#"file: %#", filename);
}
The plist configuration:
<key>NSServices</key>
<array>
<dict>
<key>NSRequiredContext</key>
<dict/>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>Upload File</string>
</dict>
<key>NSMessage</key>
<string>uploadFromPasteboard</string>
<key>NSPortName</key>
<string>Finder</string>
<key>NSSendTypes</key>
<array>
<string>NSURLPboardType</string>
</array>
<key>NSReturnTypes</key>
<array/>
</dict>
</array>
All seems fine, the service is displayed in the service menu, but when I click it, nothing happens, no logs or anything else, like its not called at all.
Could someone point me whats wrong cos I'm starting to pull my hair hardly :(
Are you setting your service provider instance? Like this (from: Providing a Service):
EncryptoClass* encryptor = [[EncryptoClass alloc] init];
[NSApp setServicesProvider:encryptor];
Merely having this method in your app delegate class is not enough. In the standard application set-up, having this in your app delegate might be sufficient:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[NSApp setServicesProvider: self];
}
Also you're specifying "Finder" for NSPortName. This is probably not correct. From Services Properties (emphasis mine):
NSPortName is the name of the port on which the application should
listen for service requests. Its value depends on how the service
provider application is registered. In most cases, this is the
application name. This property is ignored for Automator workflows
being used as services.
My reading of the documentation is that the application whose name is in NSPortName is the application that will be used to handle the service request. If the name of your app isn't "Finder" (and it shouldn't be, for obvious reasons) then your app will never be called by the service.
Here's the issue, I launch my app from Xcode and it gets up and running then I switch to settings.app and change a toggle from NO to YES and when I switch back to my app, the key outputs NO not YES.
i think i'm running up against the quote below, but not sure how to get around it, if the user launches the app, and goes to settings and changes the toggle, its now out of sync because Settings.app outputs on first launch to NO. Doesn't make sense that a user can't change the setting the first time they switch to settings.app
"For newly installed applications, default preference values from the application’s Settings bundle are not set until the Settings application runs. This means that if the user runs your application before running Settings, the default values specified in your Settings bundle are unavailable."
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *appDefaults = [NSDictionary dictionaryWithObject:#"NO" forKey:#"hideActionBar"];
[defaults registerDefaults:appDefaults];
[defaults synchronize];
and then the code i use to check it
- (void)applicationDidBecomeActive:(UIApplication *)application {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL hidden = [defaults boolForKey:#"hideActionBar"];
NSLog(#"%d",hidden);
if (hidden) {
viewController.actionButton.enabled = NO;
} else {
viewController.actionButton.enabled = YES;
}
}
and my settings Root.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
<key>Title</key>
<string>Hide Action Bar</string>
<key>Key</key>
<string>hideActionBar</string>
<key>DefaultValue</key>
<false/>
<key>TrueValue</key>
<true/>
<key>FalseValue</key>
<false/>
</dict>
</plist>
For BOOL variables you need not to set any value if want it to be NO. Because when you will try to access it first time it will return you the same. So remove registerDefaults code(all four lines) from your applicationDidFinishLaunchingWithOptions method.
Explanation:
When you change setting from NO to YES and launch your application, in applicationDidFinishLaunchingWithOptions this value is once changed to NO programmatically.
Note: Whenever you use settings bundle in your application, prior to registering a value(object value, because primitive types will return 0) check whether that value is nil(it means still unregistered), if the value for key is nil then only register with initial default values else you will end up in changing the values each time programmatically.
Thanks,
I am trying to create a new document UTI for my App so that people can share points of interest with others. From what I can understand on SO, Tutorials, and from Apple's documentation, you need to do the following:
Create a Document Type in the .plist
Create an Exported UTI that corresponds with it
Use the method: -(BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
From what I understand, as long as you did those right, you should be able to open the file through Mail without any problems. Unfortunately it isn't working for my own custom UTIs. I DO see my App in the list of "Open with..." in Mail, but when I select it, my App doesn't open at all. It just dosen't do anything at all not only when the App isn't open, but when the App is open. Mail stays up and nothing happens at all. I also checked the console using "Organizer" and there is absolutely nothing that happens.
Originally I thought my plist was wrong, so I tested opening a public UTI (I added the com.adobe.pdf document type) and my app launched just fine (though it promptly crashed because I don't actually support PDFs ;)). But the point was that it launched without any problem.
The only thing I can think of that might be a problem is HOW I am creating the file. I am creating the file in an email by the use of this method (also in the App to export):
MFMailComposeViewController *picker = [[[MFMailComposeViewController alloc] init] autorelease];
[picker setSubject:[NSString stringWithFormat:#"My place: %#",POIName]];
[picker addAttachmentData:customPOIData mimeType:#"application/customPOI" fileName:[NSString stringWithFormat:#"%#.icp",POIName]];
[picker setMessageBody:#"Check out this great place I found!" isHTML:NO];
[picker setMailComposeDelegate:self];
[self presentModalViewController:picker animated:YES];
Is there anything wrong with that?
Also, here is my plist code:
CFBundleDocumentTypes:
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>Custom POI</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.imadev.icp</string>
</array>
</dict>
</array>
UTExportedTypeDeclarations:
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Custom POI</string>
<key>UTTypeIdentifier</key>
<string>com.imadev.icp</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>icp</string>
</array>
<key>public.mime-type</key>
<string>application/customPOI</string>
</dict>
</dict>
</array>
Thanks a lot for any help!!
-Mark
I finally figured out what was wrong by pulling apart all the code.
When I change "public.filename-extension" to a string and not an array of strings it works. Don't ask me why... I think it's strange we can't use an array of file extensions. But apparently that was it.
Any ideas as to why that happened?
In my case, I could keep filename-extension as an array without problems. Instead, it was the 'public.data' that was the issue. I switched to 'public.image' and then had jpg, jpeg, and png as file extensions, and it suddenly worked.
I want to save strings as array elements in a Settings.bundle Root.plist and be able to access those strings from the Setting bundle on subsequent launches of my app. I could not find a good example of this anywhere. In xcode, I have created the Root.plist and it looks something like:
Key
iPhone Setting Schema
Strings Filename
Preference Items
Item 0 (Title- My Title)
Type Title
Title My Title
Identifier My Identifier
Values
Item 0 my string 1
Item 1 my string 2
Item 3 my string 3
This produces xml as follows:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>StringsTable</key>
<string>Root</string>
<key>PreferenceSpecifiers</key>
<array>
<dict>
<key>Type</key>
<string>PSTitleValueSpecifier</string>
<key>Title</key>
<string>My Title</string>
<key>Key</key>
<string>My Identifier</string>
<key>Values</key>
<array>
<string>my string 1</string>
<string>my string 2</string>
<string>my string 3</string>
</array>
</dict>
I am using the following code to attempt an access of the Values from the Identifier key "My Identifier":
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *mystrings = [defaults arrayForKey:#"My Identifier"];
for (int i=0;i<3;i++) {
NSLog(#"%#", [mystrings objectAtIndex:i]);
}
The value of mystrings is 0 at runtime. Consequently, the Values of "my string 1", "my string 2", and "my string 3" are not getting printed by the NSLog.
Can someone help me with this. Also, what would be the correct way to update those values?
Part of your problem is mentioned under the heading "Specifying Default Values for Preferences":
default preference values from the application’s Settings bundle are not set until the Settings application runs. This means that if the user runs your application before running Settings, the default values specified in your Settings bundle are unavailable.
All the stuff in the Settings bundle doesn't get loaded for your app's defaults database until after the user opens the Settings Application. Does this seem stupid to you? Seems stupid to me. The result is that you need to register all those defaults initially yourself. I wrote this while I was trying to figure out what was going on; improvements can definitely be made, but it should get you started:
NSURL * settingsURL = [[NSBundle bundleWithURL:[[NSBundle mainBundle] URLForResource:#"Settings" withExtension:#"bundle"]]
URLForResource:#"Root" withExtension:#"plist"];
NSDictionary * settingsDict = [NSDictionary dictionaryWithContentsOfURL:settingsURL];
NSArray * settingsArr = [settingsDict objectForKey:#"PreferenceSpecifiers"];
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
for( NSDictionary * setting in settingsArr ){
NSString * key = [setting objectForKey:#"Key"];
if( !key ) continue; // Some objects don't have keys
if( ![defaults objectForKey:key] ){
[defaults setObject:[setting objectForKey:#"DefaultValue"]
forKey:key];
}
}
Another problem that you have is that you don't get an array for PSTitleValueSpecifier keys when you do [defaults objectForKey:#"myTitlePreferencesKey"]; you just get one string. A #"DefaultValue" key/value pair is also required to be part of that item's dictionary. Initially you will get that value, which must be a string. If you change the value, you'll get the string from the Titles array which has the same index as that object in the Values array. I'm pretty sure that if you have a Values array, you also have to have a Titles array.
For the second part of your question, if you want to change the values of any of the preferences, you simply do
[[NSUserDefaults standardUserDefaults] setObject:myValue
forKey:#"myKey"];
I'm trying to add UTI for a pair of file types, matching by extension. I think I've got it setup properly - at the very least, the file extension -> UTI mapping is recognized (I declared it as an imported type in info.plist). However, when I try to get the UTI off of my test file, I get back a dynamic UTI. Here's the sample code I'm using to test these two cases:
NSString *previewTypeUTI = nil;
NSURL *previewFileURL = [NSURL fileURLWithPath:#"/Users/eblair/Desktop/EWF/thetestfile.myext1"];
[previewFileURL getResourceValue:&previewTypeUTI forKey:NSURLTypeIdentifierKey error:nil];
NSLog(#"%#", previewTypeUTI); // #1
NSString *testUTI = (NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)[previewFileURL pathExtension], NULL);
NSLog(#"%#", testUTI); // #2
[testUTI release];
NSLog #1 prints out "dyn.########" and NSLog #2 prints out "com.mycompany.filetype1". I would've expected them to print the same thing. Is there some extra step to get the file->UTI mapping working or is it a case of Launch Services needing to catch up?
Just using the extension->UTI mapping isn't an option because I want to use some APIs that take advantage of UTIs and those APIs seem to be using the file->UTI mapping.
For the sake of completeness, here's the plist entry for the imported types:
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.disk-image</string>
</array>
<key>UTTypeDescription</key>
<string>File Type 1</string>
<key>UTTypeIdentifier</key>
<string>com.mycompany.filetype1</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>myext1</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.disk-image</string>
</array>
<key>UTTypeDescription</key>
<string>File Type 2</string>
<key>UTTypeIdentifier</key>
<string>com.mycompany.filetype2</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>myext2</string>
</array>
</dict>
</dict>
</array>
Well, it's been close to a year, but I had to re-address this today and I think I stumbled across what I did wrong. It looks like you need to include public.data as a type you conform to, even though public.disk-image already conforms to public.data. So, the UTTypeConformsTo array should look like this:
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.disk-image</string>
</array>
After making this change, my above comparison of the file->UTI and extension->UTI now yield the same result.