I am working on an iphone application and I am wondering what the correct practice is for abstracting out strings. I am used to creating a file with constant strings and referencing them in my application (such as urls, port numbers or even button labels). I am wondering if this is considered good practice in Obj-C and if so what the best way to do it is? Should I make a class with the strings? Or use the ".strings" file?
p.s I may be localizing my application at a later time. I havent looked into how to do it, but I figure abstracting out my strings is a good idea while i'm developping.
Thank you!
MGA
generally, you interface with NSBundle. You use a string to read a localized version of the string (which is loaded from a localized strings file).
there are also some macros some people use to lighten the syntax, these are prefixed with NSLocalizedString. NSLocalizedString implementations use NSBundle.
imo, you should use string constants to identify the localized string you read (like you should with dictionaries and object keys).
you can declare your constants using this form (objc assumed):
extern NSString* const MONAppString_Download;
and define like so:
NSString* const MONAppString_Download = #"Download";
then access it using:
NSString * tableName = nil; // << using the default
NSString * localized =
[[NSBundle mainBundle]
localizedStringForKey:MONAppString_Download
value:MONAppString_Download // << return the string using the default localization if not found
table:tableName];
sometimes it helps to create wrapper functions to reduce the noise, especially when you use them in many places:
// #return what's set to the above variable named 'localized'.
NSString * MONLocalized_Download();
then you set up your strings files like a map, one for each localization you support.
so, whenever you need to read a string which is visible to the user, you use the above form. also consider that there are other resources to localize (nibs, images, pdfs, etc.) which you may bundle with your app. much of the work here is also abstracted by NSBundle, of CFBundle, if you prefer.
good luck
Related
I have a struct as defined by the following:
typedef struct {
NSString *SportName;
NSInteger numberOfPeriods;
CFTimeInterval periodLength;
NSString *periodName;
CFTimeInterval secondaryClockTime;
NSString *LeftSecondaryCounterName;
NSString *RightSecondaryCounterName;
bool PossessionArrow;
} GamePreset;
Is it possible to write variables of type GamePreset to a plist file?
There's no built-in support for writing an arbitrary struct as a property list. You have to write a method that converts the struct to a property list.
In theory, you could do it at run-time by parsing the result of #encode(GamePreset), which is replaced (at compile-time) by a string describing GamePreset:
NSLog(#"encode = %s", #encode(GamePreset));
...
2012-03-04 00:57:43.456 encodetest[43337:903] encode = {?=#qd#d##B}
This has serious drawbacks. One is that you don't get the field names, so your plist won't be particularly useful unless the reader knows the struct format, and any change to the struct's layout will make saved plists unreadable.
Another big drawback is that the #encode string doesn't have any information about structure padding. You have to account for it when parsing the string. I think you can use NSGetSizeAndAlignment to help with this.
If you want to risk your sanity and try this, read the “Type Encodings” section of the Objective-C Runtime Programming Guide.
I recommend you just bite the bullet and write a simple function that takes a GamePreset * and returns an NSDictionary * using hardcoded knowledge of the structure layout.
The question is quite simple: When I create a new API or Service Class should I create a custom class for the objects that are being passed around or should I just stick to an NSDictionary which simply holds the data in a key-value-style format.
Obviously there are pros and cons but where do you guys think is the threshold of using one over the other?
NSDictionary:
+ No dependencies
+ Very Flexible
+ Commonly used
+ Build-in support for NSCoding
- Structure not defined -> Runtime errors
A custom Object:
+ Structure defined
+ Property-style accessors: myObject.someProperty
- Can result in a rel. big number of classes for nested objects
Update: Included remarks from jbat100
I usually have a set of domain models, which fit better with the MVC approach of iPhone development. Having specific objects also enables you to enforce type-safety a lot easier and also reduces complexity in the long run. If you have NSDictionaries containing NSArrays and more NSDictionaries etc etc to represent your object graph, it can very quickly become unmanageable.
It really depends how much you expect your data model to change. Dealing with changes once you have a custom class based data model can be tricky especially when you have archives (NSCoding stuff) with different versions of the model (in already shipped versions of your app), you must be very careful to ensure backwards compatibility and avoid nasty run time surprises. In that respect NSDictionary based models are, as you say more flexible. However they do not allow all the customized checks and behaviour that custom classes do. Also custom classes make the data model more explicit for coders unfamiliar with the code, from my experience, developers often get sloppy (especially when inexperienced) when dealing with NSDictionary based models which can quickly result in an incomprehensible mess, so if you go down that route, document well and be disciplined!
If you need readonly access and do not need methods, you can do following:
#interface NSDictionary (MyClassCategory)
-(NSString*) someField;
#end
#implementation NSDictionary (MyClassCategory)
-(NSString*) someField {
return [self objectForKey:#"someField"];
}
#end
typedef NSDictionary MyClass;
And use it:
MyClass* myClass = ...;
NSString* value = [myClass someField];
In iPhone , I just want to store all URL at global level so that I should be able to access those by some id or something . I found out one way like implementing a category for NSObject so that we can access those members everywhere as all the classes are subclassed from NSObject . But I don't like this way .
Is there a simple and smarter way to do this. This is gonna save a lot of time each time I want to change the value of any of the URL .
Thank you all in advance !!
You can include all your url into a separate header file (*.h) that you can import into all classes which needs them.
There you can write a static constant or a preprocessor #define statement
static NSString * const kSampleURL = #"http://stackoverflow.com";
or
#define kSampleURL #"http://stackoverflow.com"
Its better you code-in all your constant attributes in some file named "Constants.h". Strings can be written down as:
static const NSString *kURL = #"www.google.com";
And include this Constants.h file in the pch file so that your constants are available throughout the project.
I'm building a version of an App I have already released, but with a few changes. It's not quite a lite/full version relationship, but they're similar enough that I'm using the same project with a different target.
I'd like to reword almost all of the strings I have used in the first version for the new version, and was wondering the best way to approach this. Rather than use #ifdef/#else statements before the declaration of each string, I was thinking of using NSLocalizedStrings. However, the actual language is still the same.
I read in this post that you can set the language yourself, so presumably I can invent my own language and set it to that. But I'm wondering if this is the best way to go about things? Any advice would be most welcome.
You can have multiple string tables for any given language (that is multiple .strings files). When you want a localised string, you can obtain it through:
NSString *str;
// Look up string in Full.strings
str = [[NSBundle mainBundle] localizedStringForKey:#"SomeKey"
value:#"DefaultValue"
table:#"Full"];
// Look up strings in Lite.strings
str = [[NSBundle mainBundle] localizedStringForKey:#"SomeKey"
value:#"DefaultValue"
table:#"Lite"];
Since the table for this method can be variable, you can even switch it at runtime. The above assumes you have a Full.strings table and a Lite.strings table.
Full.strings
"SomeKey" = "This string appears in the full version";
Lite.strings
"SomeKey" = "This string appears in the lite version";
You may not want to ship them together, if that is the case, you can configure your Info.plist to contain the name of the table to use for a specific target (if you add an entry called "TableToUse", you can get it via [[[NSBundle mainBundle] infoDictionary] objectForKey:#"TableToUse"])
NSLocalizedStrings actually is a macro defined as
#define NSLocalizedString(key, comment) \
[[NSBundle mainBundle] localizedStringForKey:(key) value:#"" table:nil]
With table parameter set as nil, the code will use the default one "Localizable" so if we add another localized string file, we should call [[NSBundle mainBundle] localizedStringForKey:value:table: directly instead of calling NSLocalizedStrings
I'd be very hesitant to invent my own language, but you probably don't need to either. If you use NSLocalizedString in the appropriate places and then use genstrings to extract these to a Localizable.strings (see the docs), then you could simple have two versions of this file and then copy the correct version in each target.
How can I use an NSString in a file where that string wasn't created?
ex.
I created a string in thisone.m, but I want to use the same sting (ie the same data) in thatone.m
the data from this string will be coming from a UITextField
If you don't have access to the thisone object, you can store the string as a ThisOne class variable, as long as you don't need a different one for each of your objects. Put the in your class (not inside a method, but outside of the #implementation)
extern BOOL theString;
The access is by
[ThisOne theString];
This is not as good as ennuikiller's answer, but it might be what you need.
I'm not sure exactly what your asking but there are many ways to share data between files (or objects). You can define it as an instance variable in one class and take a reference to the object instance in other class. You can pass the data to a method called on the other object, or you can share it as a global variable by making it an instance variable of UIApplication.
Again, without being more specific in your question, this should get you thinking along the right path.
As a simple example:
#interface MyObject : NSObject {
NSString *mystring;
}
I know some people don't like #define's, but they work well for this old-school C programmer.
In each project, I have a file "Strings.h" which contains a bunch of #define's, such as:
#define SK_String_I_Want_To_Display #"String I Want To Display"
(where SK_ is my preface to indicate "string constant".)
For localization, I have another file called "LocalStrings.h" with strings like:
#define SK_LOCAL(a) NSLocalizedString(#a, "") // keeps string defs simple
#define SK_Localized_String SK_LOCAL("Localized String")
#define SK_Another_String SK_LOCAL("Another String")
Then I just #import "Strings.h" or "LocalStrings.h" where needed. Because I have all localized strings in one file it's easy to make sure I have localized everything.
The biggest issue with this approach is that you have to be careful not to do something like this:
#define SK_Another_String SK_LOCAL("Another String");
--- as that semicolon at the end can cause tricky bugs that are hard to find.
The overhead of having the #define expanded in place is pretty low. Compiles take a bit longer if you change one of these .h files, but I find the solution works well.