How can I detect the current application language ? I am not talking about NSLocale user preferences.
In my app there are currently two supported languages. A default 'en' and a specific 'it'.
I just wanted to know which one is actually in use. If it is relevant, as a further explanation, I am providing content trough a web service only for the two supported languages. When invoking the service I need to pass a language parameter. Currently I am doing this, it works, but I totally dislike it:
NSString *preferredLanguage = [[NSLocale preferredLanguages] objectAtIndex:0];
if (![preferredLanguage isEqualToString:#"it"] && ![preferredLanguage isEqualToString:#"en"]) {
return #"en";
}
return preferredLanguage;
I have looked trough the NSLocale class and UIApplication, but didn't manage to find anything useful.
I also notice that NSBundle has some localization methods, but they all seems not specific about which one is in use.
[[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0] would be better, because it will always return a localization that your app supports. There is no need for an additional check afterwards.
In fact, your code has a bug. If the first language in the user's preferences is not one that your app supports, iOS will continue down the list until it finds one that your app does support. So if the user's preferences were "fr", "it", "en", ..., iOS would load the it versions of your resources. However, your code would fall back to en.
(This is perhaps more important on OS X, where it's easy for the user to change the language ordering. On iOS it's apparently possible to do that, but it's not as obvious how it works.)
I usually reserve a special key in Localizable.strings, such as "HTTPAcceptLanguage", which I set to "en", "fr", etc. Now telling your server the language displayed by the application is as simple as NSLocalizedString(#"HTTPAcceptLanguage", nil).
Related
I am reading through the iOS Developer Guide to get familiarized with the Objective-C language and currently I am having a little confusion on the topic of Container Literals and Subscript Notation as it pertains to creating objects like NSDictionary.
I understand that there are several ways to create NSDictionary objects including Key-Value encoding (dictionaryWithObjects:forKeys: and dictionaryWithObjectsAndKeys:, or their corresponding initializers). Source Link.
From my understanding there are two main ways to do this and then there is another way which is by using container literals, demonstrated here:
NSDictionary *myDictionary = #{
#"name" : NSUserName(),
#"date" : [NSDate date],
#"processInfo" : [NSProcessInfo processInfo]
};
Which is the best way to use? Is there any benefit in using the Container Literal technique over the previous two or is it just a convenience thing for programmers?
I am under the impression that it is also just another easier way to code things like arrays. Is this true or is there something that I'm missing here? Are these techniques just a matter of personal preference?
I disagree with the other answers posted thus far: almost all the time, it's better to use the new container literal syntax than to use constructors. They help with code correctness, and there's not really that much to worry about for compatibility.
Code Correctness
Container literals are indeed syntactic sugar, but specifically they map to the "safe" constructor methods +[NSArray arrayWithObjects:count:] and +NSDictionary dictionaryWithObjects:forKeys:count:. Constructing an array or dictionary using one of these methods directly isn't all that convenient, so many programmers find it simpler to use arrayWithObjects: and dictionaryWithObjectsAndKeys:. However, the latter methods have a nasty pitfall: since the argument list must be terminated with nil, you can find yourself with unexpected array/dictionary contents if you pass nil where you intend to pass an object.
For example, say you're setting up a dictionary mapping the properties of one of your model objects (maybe you're going to send it as JSON?):
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
person.name, #"name", person.title, #"title", person.address, #"address",
nil];
If this code runs into a Person for whom no title has been set, the resulting dictionary will be missing the #"address"key and its value. You could spend hours tracking down why some fraction of the people in your database are missing addresses (and even see the code above and tear your hair out wondering why it's not working when c'mon, I'm setting it right there!). Many of us have.
By contrast, if you use the literal form like this:
NSDictionary *dictionary = #{
#"name": person.name, #"title": person.title, #"address": person.address };
It will be expanded to something like this:
id objects[] = { person.name, person.title, person.address };
id keys[] = { #"name", #"title", #"address" };
NSUInteger count = sizeof(objects) / sizeof(keys);
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:objects
forKeys:keys
count:count];
And if person.name or person.title returns nil, this method will throw an exception instead of silently creating data you don't want. (Either way you'll have to decide how you want your code to handle nil titles, but this way you'll catch the problem sooner.) And sure, you could write this "safer" form yourself instead of using the equivalent syntactic sugar, but are you sure you won't just fall back on the habit of writing dictionaryWithObjectsAndKeys: because it's shorter?
Compatibility
The code generated by container literals (and number literals and boxed expressions, for that matter) uses no new API, so you can compile it with Xcode 4.4 or newer (or Clang 3.1 or newer directly) and deploy to any version of Foundation. You do need to consider compatibility if your source code will also be used with older compilers or GNUStep, however. (Though it sounds like GNUStep is good with Clang now, too.)
And it's not part of the question, but since it's on a related subject: the same is "sort of" true for the new object subscripting syntax. That does use new methods only defined on Mac OS X 10.6 and iOS 6.0... but those methods are provided by libarclite. (You know, the library that gets linked in when you try to deploy ARC code back to iOS 4.3 or Mac OS X 10.6 -- it's not just for ARC anymore!) So all you need to do is declare them in a header, link ARCLite if you're not already, and you're good to go.
There's no "best way". Use whichever is the best for a particular use case. For example, if you want your app to be portable (i. e. the logic that requires Foundation only and not UIKit can run on other platforms as well, like Mac OS X or Linux with GNUstep, etc.) then avoid using the literal syntax - they're not very portable. If you need it to work on iOS only, then use them, because they're convenient.
Also, these notations are only syntactic sugar - that is, they map to method names (as far as I know, exactly to the two methods you mentioned in your question), so they don't have any effect on performance, the behavior of the algorithm, etc.
And yes, you guessed it right: the same applies to the new subscripting syntax - for NSArray, it invokes - objectAtSubscriptedIndex:.
You can use them on GNU/Linux with GNUstep and clang. In most of my cases GNUstep works with clang much better than all versions of gcc. (Sorry I should just edit the other answer, I am new to this)
Have tried to use "+resetStandardUserDefaults" of the NSUserDefaults class to reset the default values to their "factory" settings. It turns out to be not the appropriate method to use. Find out from some archived discussions in SO that we should use the instance method "removeObjectForKey" to remove the user's settings instead.
But am just curious and hope that someone could explain the actual usage of "+resetStandardUserDefaults". Have read the reference web manual about this. But it is very brief and I don't seem to get what it really means ...
Somewhere (usually in the app delegate +initialize) you declare the standard user defaults (preferences of your app at the first launch).
+resetStandardUserDefaults makes your app mean it's the first launch and uses the user defaults the way you declared them in the app delegate.
THE ABOVE IS THE WRONG ANSWER
As Apple's documentation says, it:
Synchronizes any changes made to the shared user defaults object and releases it from memory
The reset portion refers to the fact that the standardUserDefaults object in memory is destroyed, so you get a fresh one from the file system next time you use [NSUserDefaults standardUserDefaults] but it has nothing to do with any first launch behaviour.
The call would make sense if it was renamed flushDefaultsToDisk or similar. It is the most misleadingly-named part of iOS SDK I've seen so far.
(I corrected this in-place as this rates quite highly on searches and didn't want to put the correction in a comment which someone might not read.)
I have done a lot of research on this and haven't found anything useful. I want am making a simple program in objective c for personal use that opens files and gives information about them. The only problem i have encountered is that i cannot find the file of a file i am opening. Without simply looking at the extension, is there a way to find the full file format of a file in objective c?
If possible, i would also like to be able to save that file in a different format. Information on this subject is also important for this application. Help will be greatly appreciated.
Mac OS X has type information attached to each file which specifies what the type of the file is supposed to be. This information is given by the application which last saved the file, so it is not necessarily correct. Also, new versions of OS X ignore this information and go entirely off of the file extension. However, the information is still stored and can be retrieved using NSFileManager's attributesOfItemAtPath:error: method.
As mentioned by quixoto above, OS X now maps extensions to UTIs. The UTI of a file can be retrieved using NSWorkspace, which can also tell you what the UTI means. This code will get the localized description of the file at /full/path/to/file:
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
NSString *description = [ws localizedDescriptionForType:[ws typeOfFile:#"/full/path/to/file" error:nil]];
The typeOfFile:error: method requires an absolute path.
Consider: what do you mean by "full file format"? What kinds of files and level of detail do you care about?
You can't really get "format" information from the file system like you want. Believe it or not, the file system has no idea what format files are in.
File systems traditionally store files as a stream of bytes with a name, a size, and some other attributes ("hidden", permissions, etc). That's all that it's responsible for, and it's the application's problem to read those bytes and interpret them to mean something useful.
The extension is the traditional hint to an application about what a file contains, but as you might guess, it's certainly not a verified guarantee.
Modern Mac OS X has Quick Look, which uses a system-wide framework that adds some smarts on top of this, with mappings between extensions and UTIs, which are a richer notion of file type. Docs are here. I don't think there's a way to query this system for the mappings between extensions and UTIs, but I'm not sure.
Loading a file of one format and saving in another is 100% dependent on the file types you're talking about, and you're going to have ask very specific questions about specific formats if you really care to accomplish this. (And that topic extends well beyond a couple built-in method calls in Cocoa.)
This information is now (since OS X 10.5) handled through UTIs.
You can get it like this:
NSURL *desktopURL = [[[NSFileManager defaultManager] URLsForDirectory: NSDesktopDirectory
inDomains: NSUserDomainMask] lastObject];
NSURL *workingdirURL = [desktopURL URLByAppendingPathComponent:#"WorkingDir" isDirectory:YES];
NSArray *docURLs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:workingdirURL
includingPropertiesForKeys:#[NSURLTypeIdentifierKey] options:NSDirectoryEnumerationSkipsHiddenFiles
error: nil];
NSMutableArray *pdfURLs = [NSMutableArray array];
for (NSURL *docURL in docURLs) {
id resourceValue;
BOOL found = [docURL getResourceValue: &resourceValue
forKey: NSURLTypeIdentifierKey
error: nil];
if ( found && [resourceValue isEqual:#"com.adobe.pdf"]) {
[pdfURLs addObject: docURL];
}
}
You can see the docs for more information about the file properties that can be retrieved this way. There is quite a lot of information available through this method.
so I'm working on my little Mac app, and I want control Adium with it. AppleScript is very cool:
tell application "Adium"
go away with message "Zoned in for Maths."
end tell
(If you're wondering what this is supposed to be. In order to actually start studying I need to create application that will change my IM status, of course ... )
I tried it in Script Editor, it worked, and I'm pretty sure that calling AppleScript from Cocoa application is gonna be trivial.
But.
Is AppleScript the only way? I don't mind using AppleScript, but it looks like programming for noobs.
Is there any way to do the same thing as above code does without AppleScript, in plain Objective-C somehow?
Would someone point me to relevant documentation? I tried Google but it was like I don't even know what I'm looking for.
Thanks!
Interprocess communication in Mac OS X is done by something called Apple Events. AppleScript is one way to send and receive Apple Events to other applications.
Therefore, you just need to construct Apple Events directly and send it to the other app, from Objective-C or whatever other language.
Honestly, if you just want to change the status of Adium, it's easiest to use NSAppleScript and pass what you just wrote, from inside Objective-C.
If you want to do more complicated stuff, Scripting Bridge is the way to go. This mechanism maps Apple Events' object hierarchy to Objective-C's object hierarchy.
If you think that's still a newbie's way, you should directly create Apple Events via NSAppleEventDescriptor.
Well, some of us old timers think using Objective-C is a sissy's way. If you think so, you should directly deal with C structs called AEDesc and such. See Apple Events programming guide and the corresponding reference.
However, I think people who use OS X are all noobs. Real people use Linux.
My dad would say people who use GUI are just too spoiled.
The point is, you don't have to care whether it is a newbie's way or not. The important thing is whether you can achieve what you want. In fact, AppleScript is a very powerful, dynamical language, whose power is not well appreciated by many people. Read AppleScript language guide and be surprised.
The simplest way would be to use NSAppleScript
NSAppleScript *script = [[NSAppleScript alloc]
initWithSource:#"tell application \"Adium\" to go away with message \"Zoned in for Maths.\""
];
For a more powerful way of accessing scripting, use Scripting Bridge. Scripting Bridge requires at least the 10.5 SDK. You first need to prepare your app.
Use the sdef and sdp command line utilities to generating header files for the applications you wish to control (see "Preparing to Code" for details).
Add the generated header to your project.
Add the ScriptingBridge framework to your project. After that, you can use Objective-C calls to control the other application.
After that, you can use Objective-C to send scripting commands to the application.
AdiumApplication *adium = [SBApplication applicationWithBundleIdentifier:#"com.adiumX.adiumX"];
for (AdiumAccount* acct in [adium accounts]) {
[acct goAwayWithMessage:(AdiumRichText *)#"Zoned in for Maths."];
}
Status messages are Adium's rich text type (which is NSTextStorage under the hood), but it's convertable from plain text, so passing an NSString rather than a true AdiumRichText should work fine.
There are a few hoops to jump through. For example, you can't create scripting objects in the target application by using its ObjC classes directly; you must use classForScriptingClass: to get the class, which you can then use to create objects as normal (i.e. alloc and init, initWithProperties &c.).
// creating an AdiumContactGroup
NSDictionary *props = [NSDictionary
dictionaryWithObjectsAndKeys:
#"mathies",#"name",
nil
];
AdiumContactGroup *mathies= [[[[adium classForScriptingClass:#"contact group"] alloc]
initWithProperties:props]
autorelease];
if (mathies) {
[[adium contactGroups] addObject:mathies];
}
Note that other languages (such as Python and Ruby) also have Scripting bindings.
Others have mentioned sending full Applescript or using the Scripting Bridge. A third choice is to use Appscript which is also available for Python and Ruby. It is a little cleaner (IMO) than using the Scripting Bridge in some ways. And definitely easier. Although in other ways Scripting Bridge is better. It also has the advantage of an application called ASTranslate which will translate most Applescript calls into Appscript. Here's the Appscript for your little Applescript example.
ADApplication *adium = [ADApplication applicationWithName: #"Adium"];
ADGoAwayCommand *cmd = [[adium goAway] withMessage: #"Zoned in for Maths."];
id result = [cmd send];
While playing with RubyCocoa, I keep progressing with my idea for my application. Because my application will be going to use configuration files, I would like to know how I discover the relative path to store these inside my application structure (or if a better idea emerges, please elaborate also the "why").
Also good for me to know is to discover environment variables, such as operating system version, the amount of memory that is available and such. Hyperlinks would be awesome too.
Please notice I use RubyCocoa and thank you for your feedback, comments and answers!
To access inside the application bundle, you use NSBundle. See NSBundle reference. In Obj-C, you use +[NSBundle mainBundle] to get the main bundle of the app. Then you use -[NSBundle pathForResource:ofType:] to get the file. I don't know RubyCocoa syntax, but I assume you know how to translate to it :)
If by the configuration file you mean a user-configurable things, remember you can't write inside the app bundle at runtime. Instead one uses NSUserDefaults. See User Defaults Guide.
Here's some Cocoa code I use to write all the environment variables to the console. Again, I don't use RubyCocoa, so you'll have to translate:
NSProcessInfo *myProcessInfo = [NSProcessInfo processInfo];
NSDictionary *env = [myProcessInfo environment];
for (id key in env)
{
NSLog (#"key: %#, value: %#", key, [env objectForKey: key]);
}