Get ios multi-value user preferences - settings.bundle

I try to retrieve the value selected in Settings Application using "Settings Bundle". The field is "PSMultiValueSpecifier". In "Root.plist" I implemented:
Type: PSMultiValueSpecifier
Title: Abcdefg
Key: abcdefg
DefaultValue: aaa
Values (array):
Item 0: aaa
Item 1: bbb
Titles (array):
Item 0: A
Item 1: B
And, In the implement file I have wrote:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *string = [defaults objectForKey:#"abcdefg"];
But "string" is empty. When I do the same with a user default of kind: "PSTextFieldSpecifier" I can retrieve the value.
I follow the instructions explained in the book "Apress - Beginning iOS 5 Development: Exploring the iOS SDK" but I can not retrieve the value selected.
Instructions are the same of the "Apple Development Help".
I do not understand what is the problem. It seems easy.

I have found the bug.
If you implement "Multi Value" user preferences in "Settings.bundle" (with its "Default Value"), but never enter in settings iPhone simulator and never change the multi-value preference, the sentence:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *string = [defaults objectForKey:#"key"];
Returns a "nil" value. Incredible, I lost a week trying to find the problem!!!!
But I have a doubt:
If I implement a "Multi Value" user preferences and I publish the App in iTunes Store, but user not use this "Multi Value" user preferences, "NSUserDefaults" will return nill value?
Do I have to consider the possibility of receiving an empty value?
Really, I think it is ridiculous.

It seems that DefaultValue is used only in the official Settings app. Apparently, you have to register the defaults by 1) testing one of your settings to see if it's NULL, then 2) iterating through the settings, grabbing and setting the defaults:
sample Obj-C code for this by just.do.it
The documentation on PSMultiValueSpecifier does seem deficient here. Confusion on this shows up all over SO and the web. For example:
defaultvalue-for-psmultivaluespecifier on SO
app-preferences-how-to-get-psmultivaluespecifier-property on SO

No, I think you need to register your default value, for example in:
-(BOOL) application: (UIApplication*) application didFinishLaunchingWithOptions: (NSDictionary*) launchOptions
{
NSDictionary* defaults = #{#"key": #"default value"};
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
}
This is because the app knows nothing about the settings bundle it contains. The page 430 of book Beginning iOS6 Development: Exploring the iOS SDK has a clear explanation of this.

if the returned object is nil then set the default value.
NSString * key = #"the key";
NSUserDefaults * uD = [NSUserDefaults standardUserDefaults];
NSString * stringValue = [uD objectForKey:key];
if (!stringValue) {
stringValue = #"the default value";
[uD registerDefaults:#{key: stringValue}];
}

Related

Xcode localization

I have a question on how to set the default language in an Xcode project.
My Mac OS X App supports German and English.
Everytime English is not selected in Systemsettings, default language is German.
I want to switch default language to English, so that non-german users get an English UI.
Changing "Localization native development region" in Plist file to English didn't solve the problem.
I tried to do it within the code but this is not what Apple recommends in their HIGs.
Sampleproject is hosted on Github.
https://github.com/christian123456/xcodelocalization
Xcode Version is 5.1.1
I recently added the screenshots to the repository. As you can see in the "german incorrect.png" screenshot, French and Portuguese languages are chosen but Mac OS picked German as language. I want the language to be English.
Testing and analyzing
I've added -NSShowNonLocalizedStrings YES into scheme-editor > Run .app > Arguments > Arugments Passed On Launch. This turns any string it cannot find a localized variant for into uppercase.
The result is, I see all uppercase.
To switch language for developing use -AppleLanguages (it).
As result, the en localization appears.
To switch the language of an app, you can also use the following app:
https://itunes.apple.com/de/app/app-language-chooser/id451732904?l=en&mt=12
For more information read here.
Solution
After a little searching and testing, I found this solution:
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:#"en", #"de", nil] forKey:#"AppleLanguages"];
[[NSUserDefaults standardUserDefaults] synchronize];
This would set the language english as default.
Another option is to create your own localization class. Call your class, and it can force localization into any language you want:
- (NSString*) MyLocalizedString:(NSString*) label;
{
NSUserDefaults* defs = [NSUserDefaults standardUserDefaults];
NSString* preferredLanguage = [defs objectForKey:#"LocalizedLanguage"];
NSString *path = [[NSBundle mainBundle] pathForResource:preferredLanguage ofType:#"lproj"];
NSBundle* languageBundle = [NSBundle bundleWithPath:path];
NSString *result = [NSString stringWithFormat:#"%#",[languageBundle localizedStringForKey:label value:#"" table:nil]];
return(result);
}
- (NSLocale *) forceEnglishLocalization;
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:#"en" forKey:#"LocalizedLanguage"];
NSLocale * currentLocale;
currentLocale = [[NSLocale alloc] initWithLocaleIdentifier:#"en"];
return(currentLocale);
}
Need help localizing your Xcode apps?
https://itunes.apple.com/mg/app/generate-localizable-strings/id890673579?mt=12

History of previously connected bluetooth devices using MCNearbyServiceBrowser

I'm using MCNearbyServiceBrowser to discover nearby bluetooth devices & inviting using invitePeer: toSession: withContext: timeout:.
Now the problem is that i want to keep history of all connected devices & re-connect them in future (manually by clicking them in UITableview) if the are nearby again. Is this possible? And is it compatible according to Apple's policy? Thanks.
UPDATE:
I already achieved the goal as "jamdaddy25" answered (before he answered :P) & it's working fine too.. But what if two devices have the same name? In that case it will be a problem of not showing one of the two or more devices in the list. And i don't want that. BTW thanks for response.
You could keep the peerID display name. This is a property on MCPeerID. When you have started browsing, either prior to user selecting a previously connected peer or starting up browsing upon coming to that screen, cycle through the nearby peers and see if u have a display name match. If so, you can systematically send an invite and reestablish the connection.
You could even do this so the only selectable previous connections are those which are currently nearby peers.
Update:
So to make sure that you don't have peer name collisions you need to make the names unique. The best way I know how to do this is to create a UUID based name. I save this inside of a simple object (UserPeerInfo below) and saving / load this to NSUserDefaults so this peer name will be used always for this peer
// Initialize with any stored data
if (!_userPeerInfo) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"userPeerInfo"]) {
NSData *userPeerInfoData = [defaults objectForKey:#"userPeerInfo"];
_userPeerInfo = (UserPeerInfo*)[NSKeyedUnarchiver unarchiveObjectWithData:userPeerInfoData];
} else
{
NSString *peerName = [[NSUUID UUID] UUIDString];
_userPeerInfo.peerName = peerName;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// Create an NSData representation
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:_userPeerInfo];
[defaults setObject:data forKey:#"userPeerInfo"];
[defaults synchronize];
}
}
Then when setting the name of your peer and initializing, use that peer name like normal
self.peerId = [[MCPeerID alloc] initWithDisplayName:self.userPeerInfo.peerName];
self.advertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:self.peerId discoveryInfo:info serviceType:kServiceType];
self.advertiser.delegate = self;
[self.advertiser startAdvertisingPeer];

Check for first launch of my application

How would I check if it is the first launch of of my application using NSUserDefaults and running some code for the first time my app opens?
This should point you in the right direction:
static NSString* const hasRunAppOnceKey = #"hasRunAppOnceKey";
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
if ([defaults boolForKey:hasRunAppOnceKey] == NO)
{
// Some code you want to run on first use...
[defaults setBool:YES forKey:hasRunAppOnceKey];
}
The NSUserDefaults answer is the first thing that popped in my head, but upon reflection I will make another suggestion. A bit more work, but it's worth considering. The motive is: sometimes when troubleshooting an app, Apple recommends deleting that app's plist file. It's a fairly ubiquitous troubleshooting technique. I would recommend storing your boolean in your plist file instead of NSUserDefaults.
Disclaimer: I only do iOS development, so I'm not sure how NSUserDefaults and plists interact on the Mac, and I don't know what all is involved in getting your plist to live in ~/Library/Application\ Support/Preferences/com.mycompany.MyAppName.plist
Anyway, I imagine what this requires is having some code which can actually author a "fresh" plist (probably a copy from a template file in your bundle), and you app does this if it launches and does not see a plist. The default plist should not include the flag which lets your users skip the 'first time' code, but if they have opened the app before, and then delete the plist, they should get default behavior back.
This is an important behavior to support where possible, to aide our users if our app ever gives them trouble.
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"hasBeenLaunched"]) {
// Run code on the first launch only ...
[defaults setBool:YES forKey:#"hasBeenLaunched"];
}
You can use NSUserDefaults to save bools, integers, objects into the program and have them available whenever you open it. You can use 'boolForKey' to set a flag called "hasBeenLaunched". By default, this value will be NO when not set. Once you change it to YES, the code in the if condition will never be executed again.
In your main controller class, implement something like this:
static NSString * const MDFirstRunKey = #"MDFirstRun";
#implementation MDAppController
+ (void)initialize {
NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
[defaults setObject:[NSNumber numberWithBool:YES] forKey:MDFirstRunKey];
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
// the following if on Mac and is necessary:
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:defaults];
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
BOOL firstRun = [[[NSUserDefaults standardUserDefaults]
objectForKey:MDFirstRunKey] boolValue];
if (firstRun) {
// do something
[[NSUserDefaults standardUserDefaults] setObject:
[NSNumber numberWithBool:NO] forKey:MDFirstRunKey];
} else {
// do something else
}
}
#end
The +initialize class method is called before an instance of the class it's found in is created; in other words, it is called very early on, and is a good place to set up your default values.
See Preferences and Settings Programming Guide: Registering Your App's Default Preferences for more info.

Interface language in MFMessageComposeViewController

I'm trying to send sms from my iphone using MFMessageComposeViewController. It shows a modal dialog with filled fields. everything work, but I want to show dialog in Russian Language.
I want to see all values (such as "New message", "send", etc) in Russian.
I'v checked default language:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *languages = [defaults objectForKey:#"AppleLanguages"];
NSString *currentLanguage = [languages objectAtIndex:0];
NSLog(#"Current Locale: %#", [[NSLocale currentLocale] localeIdentifier]);
NSLog(#"Current language: %#", currentLanguage);
And it returns, that language is ru-RU.
How can I set a lang for this dialog?
You need to localize your app for Russian in order for it to show the localized controls you ask for.
All you should need to do is add a ru.lproj folder to your project (you could include here a Localizable.strings file if needed by your app) to let know Xcode that you are actively supporting that language.
For every language you add as language-code.lproj, every native control (MFMessageComposeViewController, MFMailComposeViewController) should consider using that language if set as local on the device.

How to know if window is minimizable when titlebar was double clicked?

This image is from SystemPreferences > Appearance
I want to know How do I get that value programmatically?
I ask because I am drawing a window with a customized titlebar and I want it to resemble (in behavior) as much as possible to normal (non-customized) cocoa windows.
Maybe a terminal command I can pipe or is there an cocoa API that does this?
EDIT:
Answer (thanks to NSGod)
- (void)mouseUp:(NSEvent *)event{
if ([event clickCount] == 2) {
//Get settings from "System Preferences" > "Appearance" > "Double-click on windows title bar to minimize"
NSString *const MDAppleMiniaturizeOnDoubleClickKey = #"AppleMiniaturizeOnDoubleClick";
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
// [userDefaults addSuiteNamed:NSGlobalDomain]; // unnecessary
BOOL shouldMiniaturize = [[userDefaults objectForKey:MDAppleMiniaturizeOnDoubleClickKey] boolValue];
if (shouldMiniaturize) {
[self miniaturize:self];
}
}
}
Later I found that Appearance (Aqua/Graphite) can be found:
NSString * const kAppleAquaColorVariant = #"AppleAquaColorVariant";
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
// [userDefaults addSuiteNamed:NSGlobalDomain]; // unnecessary
NSNumber *color = [userDefaults objectForKey:kAppleAquaColorVariant];
if ([color intValue] == 6) {//graphite is 6
imageName = [imageName stringByAppendingFormat:#"_graphite"];
}else{//defaults to aqua, (aqua is 1)
imageName = [imageName stringByAppendingFormat:#"_colorsryg"];
}
Which can be helpful too :)
The way I would do it is probably read the value in from user defaults.
NSString * const MDAppleMiniaturizeOnDoubleClickKey = #"AppleMiniaturizeOnDoubleClick";
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
// [userDefaults addSuiteNamed:NSGlobalDomain]; // unnecessary
NSNumber *miniaturize = [userDefaults
objectForKey:MDAppleMiniaturizeOnDoubleClickKey];
NSLog(#"AppleMiniaturizeOnDoubleClick == %#",
([miniaturize boolValue] ? #"YES" : #"NO"));
(This preference setting is stored in the invisible .GlobalPreferences.plist in your ~/Library/Preferences/ folder).
Note that by default, the "double-click to minimize" option is turned off, so if you check for the presence of the AppleMiniaturizeOnDoubleClick and it returns nil, that means it is off. (User defaults only start to store values if they differ from the defaults).
This key is the same in Leopard as it is in Snow Leopard. (It hasn't changed in Lion or Mountain Lion either).
Of course, there is a secret (private) method in NSWindow, -(BOOL)_shouldMiniaturizeOnDoubleClick, but I wouldn't recommend using private methods.
[UPDATE] Regarding Catfish_Man's comment: you are correct in that the line [userDefaults addSuiteNamed:NSGlobalDomain]; is unnecessary, as NSUserDefaults already has the ability to read global preferences. (I modified the code above to reflect this).
"Additionally, NSGlobalDomain is not translated to
.GlobalPreferences.plist for that method."
I'm not sure I follow you there. NSUserDefaults is built on top of CFPreferences which defines the following 6 constants:
Application:
kCFPreferencesAnyApplication,
kCFPreferencesCurrentApplication
Host:
kCFPreferencesAnyHost,
kCFPreferencesCurrentHost
User:
kCFPreferencesAnyUser,
kCFPreferencesCurrentUser
Given a fictional application bundle identifier of "com.markdouma.App" and a single host (based on your current network location that won't change for this example), there are generally 8 locations where preference information could be stored on your disk. (NOTE: The paths shown are for demonstration purposes only; the exact file path locations are subject to change). The 8 different locations arise from the different combination of the CFPreferences constants:
/Library/Preferences/.GlobalPreferences.plist
(kCFPreferencesAnyApplication, kCFPreferencesAnyUser, kCFPreferencesAnyHost)
/Library/Preferences/com.markdouma.App.plist
(kCFPreferencesCurrentApplication, kCFPreferencesAnyUser,
kCFPreferencesAnyHost)
/Library/Preferences/ByHost/.GlobalPreferences.UNIQUE_HOST_IDENTIFIER.plist (kCFPreferencesAnyApplication, kCFPreferencesAnyUser, kCFPreferencesCurrentHost)
/Library/Preferences/ByHost/com.markdouma.App.UNIQUE_HOST_IDENTIFIER.plist (kCFPreferencesCurrentApplication,
kCFPreferencesAnyUser, kCFPreferencesCurrentHost)
~/Library/Preferences/.GlobalPreferences.plist
(kCFPreferencesAnyApplication, kCFPreferencesCurrentUser,
kCFPreferencesAnyHost)
~/Library/Preferences/com.markdouma.App.plist
(kCFPreferencesCurrentApplication, kCFPreferencesCurrentUser,
kCFPreferencesAnyHost)
~/Library/Preferences/ByHost/.GlobalPreferences.UNIQUE_HOST_IDENTIFIER.plist (kCFPreferencesAnyApplication,
kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)
~/Library/Preferences/ByHost/com.markdouma.App.UNIQUE_HOST_IDENTIFIER.plist (kCFPreferencesCurrentApplication,
kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)
While NSUserDefaults can only write to the domain combination shown in italics, it automatically has read access to the domain combinations shown in bold. In other words, without having to do anything, I can automatically run the following code and print the results:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSNumber *miniaturize = [userDefaults
objectForKey:#"AppleMiniaturizeOnDoubleClick"];
NSNumber *fastUserSwitching = [userDefaults
objectForKey:#"MultipleSessionEnabled"];
NSLog(#"AppleMiniaturizeOnDoubleClick == %#",
([miniaturize boolValue] ? #"YES" : #"NO"));
NSLog(#"MultipleSessionEnabled == %#",
([fastUserSwitching boolValue] ? #"YES" : #"NO"));
Running that code on my system prints the following results:
AppleMiniaturizeOnDoubleClick == YES
MultipleSessionEnabled == YES
This makes sense, since I have both Fast User Switching and Double-click to minimize options enabled. MultipleSessionsEnabled is stored in the local domain at /Library/Preferences/.GlobalPreferences.plist, and AppleMiniaturizeOnDoubleClick is stored in the user domain at ~/Library/Preferences/.GlobalPreferences.plist.
Sample project: NSUserDefaultsFinagler.zip
"Additionally additionally, that's slow. Please don't do this."
Sorry, but that makes no sense (assuming that we've agreed that we're no longer using addSuiteNamed:). User defaults are cached by the application, making calls take in the matter of milliseconds. Wouldn't there be little noticeable difference between asking user defaults for the value for a key that represents a local application value or one that represents a global value?
AFAIK, this is the only "legal" (App-store-compatible) way to achieve the OP's goal. If there's another more efficient means, then please elaborate.