How to use NSLocalizedString in IB [iPhone SDK]? - iphone

When I assign text programmatically, I can use NSLocalizedString(....), but if I am writing the text directly in IB (say to a UILabel) how do I localize it?

One solution is to create multiple localized nib files. This probably works best if your localization needs are very straightforward and you're simply going hand your resources over to expert software localizers at the end of development.
If you're localizing while you're developing and the UI is changing rapidly, duplicate nib files can be a big pain, since every UI tweek must be duplicated in each local version of the nib. To avoid this, you'll need to write some code in your view controllers to handle setting the localized strings, typically in the view controller's -viewDidLoad method. Make sure that every control with localized text is an IBOutlet and wired them up to your view controller in IB. Then your view controller's -viewDidLoad will look something like this:
- (void)viewDidLoad {
[super viewDidLoad];
hello.text = NSLocalizedString(#"Hello", #"Hello label");
world.text = NSLocalizedString(#"world", #"world label");
// ... etc
}
You do this type of view setup in -viewDidLoad since the objects in your nib aren't fully created until after your -init method runs. -viewDidLoad runs after -init but before your view becomes visible.

This is a large and complex topic. A good starting place is Introduction to Internationalization Programming Topics and there is also Preparing Your Nib Files for Localization

You can create multiple versions of a .nib file for each locale. There are also tools to let you edit the strings in them easily. Apple has pretty good documentation on this.

I also was searching for a solution, not necessarily for iPhone, but XCode/IB in general. All references did not deal with the fact that you might need a key internally to indicate a state and want to display a localized string for the user in a label or text cell corresponding to that key. I did not found in first step a standard approach how to store e.g. a key value for a key in shared user defaults and display the localized string for that key value in a label.
I found a solution wich does not need many coding and is compliant with the bindings in ib.
First you provide a file Localizable.strings e.g. with a line containing
"MyKeyValue" = "Localized display label";
Now you can localize the key value with: NSLocalizedString(aKeyValue,nil).
In the label you did not find any value transformer dealing with NSLocalized String.
So I created a class KeyToLocalizedStringTransformer to transform a key value into a localized string:
#interface KeyToLocalizedStringTransformer : NSValueTransformer {}
#implementation KeyToLocalizedStringTransformer
+ (Class)transformedValueClass
{
return [NSString class];
}
+ (BOOL)allowsReverseTransformation
{
return NO;
}
- (id)transformedValue:(id)aValue
{
NSString *NLString = [NSString stringWithString:NSLocalizedString(aValue,nil)];
return NLString;
}
Last step for preparing is to register the transformer e.g. in +initialize:
NSValueTransformer *transformer = [[KeyToLocalizedStringTransformer alloc] init];
[NSValueTransformer setValueTransformer:transformer forName:#"KeyToLocalizedStringTransformer"];
Now you can use a value transformer in the bindings for the text field or cell (Simply type in the name if you do see only the NSUnArchiveFromData and so on...)
Sorry no image here from IB 'cause I am new here and "have no reputation": you have to imagine the binding to the shared user defaults controller, Kontroller key: values, Model Key Path: MyStateKey and a value transformer as described.
As a result you dont have to do anything in the nl duplicated nib with the label, simply translate the string in the Localizable.strings.

from iOS 7 & Xcode 5 on, you should avoid using NSLocalizedString where possible. The preferred method is called 'base localisation', and works through the Storyboard. It will save you a lot of work. If you google for 'base localisation' you'll find enough tutorials to get you going

Related

What is the meaning of the console message: "snarfed from ivar layout..."?

I have a console message that appears to be triggered by apparently unrelated events.
The message states:
snarfed from ivar layout: [propertyName] = [constantString]
Where [propertyName] is the name of a property to which I set the value of a string constant [constantString].
What causes this message and what does it means?
I also ran into this issue recently. I was able to fix my specific issue, but I don't think that is exactly what the questioners are running into, since my issue was only being exposed in VoiceOver mode. As such, I'll offer thoughts on what I think is generally occurring and then I'll speak to my specific issue.
As for the general issue, I think that the Apple Framework is deciding to look through all of the ivars of a particular class in order to extract some information that it wants, but that is not provided by other parts of the UI element. This seems a little bizarre to me, but that is what I encountered.
So, to continue with the general case, and in answer to the initial question. If you're like me, then your property name is probably the same as your ivar. Try explicitly defining a getter method for that property. Then, set a breakpoint within that getter if you will be returning a non-nil value. Look at the stacktrace and that should tell you which piece of the apple frameworks is deciding to loop through your ivar layout in order to get the information it wants. (If you're not using the the same name for your property and ivar, then just define a property and getter with the ivar name and do the same thing with the breakpoint.)
My specific case was for a Custom Table Cell (like one of the commenters). In that cell,I had a property that was the same name as its ivar. I also had an explicitly defined getter for that property. I also referenced that custom table cell from the Nib file. So, it looked something like this:
class CustomTableViewCell:UITableViewCell
{
NSString *s ;
}
#property(nonatomic,retain) NSString *s ;
and in the implementation:
#synthesize s ;
-(NSString *)s
{
if( !s )
return self.reuseIdentifer ;
return s ;
}
I put a breakpoint in the return self.reuseIdentifier line,and that showed me a stacktrace from the Accessibility functions. The stacktrace showed my method being called by an Apple internal method that was looping through all of my ivars looking for something to use as the accessibilityLabel for my table cell. The name of the selector is '_accessibilityRetrieveTableViewIvarsText'.
To make matter worse, in my case, this was not just a debugger issue, it was messing up my Accessibility interface by using the wrong thing as the accessibilityLabel.
I came up with 3 fixes for my specific problem:
1) I added a value for the accessibilityLabel for the table cell inside the Nib. This satisfied the Apple framework to the point where it did not go searching through my ivars. This was not the solution I went with, however, because I did not want a static accessibility label.
2) I subclassed my CustomTableViewCell with an empty implementation and interface, and I used that as my Table cell class inside the Nib. This solved the problem because the Apple Framework looped through that class's ivars, of which there were none, and there weren't any values to 'snarf'. I did not use that solution either, but it might be the best one because it keeps Apple's frameworks from inspecting my ivars.
3) The solution I decided on was to make my ivar private and to define the property with a different name. That seems to be the standard way that a lot of folks use properties. This is what it looks like:
class CustomTableViewCell:UITableViewCell
{
#private
NSString *_s ;
}
#property(nonatomic,retain) NSString *s ;
and in the implementation:
#synthesize s = _s ;
-(NSString *)s
{
if( !_s )
return self.reuseIdentifer ;
return _s ;
}
This fixed the problem because nil is returned when Apple inspects the ivar, and, thus, nothing is 'snarfed'. I'm still not sure whether this or #2 is more appropriate.
"snarfed from ivar" basically autofills your accessibilityLabel. If you do that yourself, the message goes away, as there is no more need for sneeking into your UITableViewCell.
For future reference. The message is logged by the accessibility framework, which apparently looks through an UIView ivars for strings.
If you have a custom subclass you can define the custom attributes as specified in the following link:
Accessibility Programming Guide
Alternatively you can make the view subclass not accessible:
- (BOOL)isAccessibilityElement
{
return NO;
}
However, note:
If your application contains a custom individual view with which users need to interact, you must make the view accessible.

Get the name of the current iPhone xib being viewed

I have 3 xib files connected to the same class to reduce duplicate coding. However, there are different stuff that I want done when the new nib file loads (i.e. in the ViewDidLoad method). I've thought about doing an if statement to compare the name of the xib currently being displayed to a string. I have been trying to figure out how to do that for most of the day but I haven't had any luck. Here's some pseudo code if that confused you:
if (currentXibInDisplay == #"XibFileName1")
// Do This...
else if (currentXibInDisplay == #"XibFileName2")
// Do This...
There is a way to do that, right? It seems pretty simple but I am pretty stumped right now. Thanks for any info you can provide.
I tried to use it with
self.view.nibName;
But it won't work, instead try this
self.nibName;
You can use the nibname
if you say: NSString *name = self.view.nibname you can proove it with
`[name isEqual:#"XIBName"]`
The view controller that you're loading has the initWithNibNamed: method, you can override there and store which XIB is going to be loaded.
ViewController subclasses have a nibName string property you can check against:
[self.nibName isEqualTo:#"XIBFileName"]

Effective way to design and manage iPhone application similar to settings

I would just like to clarify that by 'design', I mean software design, not UI design.
I have an application similar to the native settings app. The problem I have with it is it doesn't follow the same clear-cut MVC style. Other apps tend to focus around displaying one kind of thing. In the case of a periodic table app for example, it's elements. The elements clearly comprise the model, and they share similar properties and behaviours, meaning they can be displayed and interacted with identically. An app like this almost designs itself!
My app, like the settings apps, consists of an arbitrary selection of rows displaying dissimilar data in dissimilar ways. One row might contain a switch, the other might modally present a very specific view when tapped. They're all very different.
How do you design something like this?
At the moment, I'm doing it all in the view controller, and the relevant rows are being tracked via an enum:
enum {
kNameRow,
kGenderRow,
kJobTypeRow,
kLevelOfExerciseRow,
kEmailAddressRow,
kTelephoneNumberRow
};
As I described, these cells are all very different, so displaying cells is handled like this:
// - tableView:cellForRowAtIndexPath pseudocode.
switch (indexPath.row) {
case kNameRow: // create name cell.
case kGenderRow: // create gender cell.
case kJobTypeRow: // create job type cell.
case kLevelOfExerciseRow: // create level of exercise cell.
case kEmailAddressRow: // create email address cell.
case kTelephoneNumberRow: // create telephone number cell.
}
And interacting with cells is handled similarly:
// - tableView:didSelectRowAtIndexPath pseudocode.
switch (indexPath.row) {
case kNameRow: // do name-specific stuff.
case kGenderRow: // do gender-specific stuff.
case kJobTypeRow: // do job type-specific stuff.
case kLevelOfExerciseRow: // do level of exercise-specific stuff.
case kEmailAddressRow: // do email address-specific stuff.
case kTelephoneNumberRow: // do telephone number-specific stuff.
}
This seems hugely unwieldy, and has the added of problem of not working when the table is broken down into multiple sections.
Is there a better way to do this? Are there any design patterns I would benefit from using when working with big tables of largely unrelated data?
Any tips at all are hugely appreciated.
I've become fond of implementing section controllers that pull the logic out of you UITableViewController subclass (or other hosting controller) and move them into self-contained classes.
I ended up implementing a base protocol that defines what a section controller needs to do - for me, that includes the number of rows in a section and a cell for the row (don't need the whole index path since the controller deals with a single section). I've got optional method for returning a section name and row height. That's all I've implemented so far since that's all I've actually needed.
It works for me because my individual sections tend to be homogeneous, but you could easily use the idea to return heterogeneous cells within the same section or refactor the idea to have cell type controllers instead of section controllers. In the end, my UITableViewDelegate and UITableViewDataSource methods just need to figure out which section controller to call instead of embedded all the logic within the UITableViewController subclass.
I think I got the idea from this article, but I also saw a more recent article that describes the same idea.
you might want to look at coreyfloyds project http://github.com/coreyfloyd/Generic-Heterogeneous-Table-Views i think this might have the functionality you need.
Here's my suggestion - handle each cell as a member of the view.
lol, it's been a while since I've used a table, so I could just be talkin' crap here but give it a try.
instead of an enum use:
NSThingyCell *nameRow;
NSThingyCell *genderRow;
#property IBOutlet NSThingyCell *nameRow;
#property IBOutlet NSThingyCell *genderRow;
- (IBAction) nameRowChanged:(id)sender;
- (IBAction) genderRowChanged:(id)sender;
and then instead of a table call with a switch, just wire each individual cell up in Interface Builder.
This has the added benefit of being row-independent, so if you have to put "ageRow" in between name and gender, nothing gets screwed up.
This will also get pretty big, so if your view has several tables, you may want to consider splitting those tables out into separate nibs/controllers and loading the views at run-time.
Have you ever thought of simply having an array of objects for a class which contains a UI element and some other identifiable data?
#interface settingsOption {
NSString *key;
UIView *displayElement;
}
+ (settingsOption *)optionWithKey:(NSString *)key andDisplayElement:(UIView *)displayElement;
#property (nonatomic, retain) UIView *displayElement;
#property (nonatomic, retain) NSString *key;
#end
Where the class method would look like
+ (settingsOption *)optionWithKey:(NSString *)key andDisplayElement:(UIView *)displayElement;
settingsOption *option = [[settingsOption alloc] init];
option.key = key;
option.displayElement = displayElement;
return [option autorelease];
}
Your settings class would have an array of settingsOption instances.
- (void)somewhereInMySettingsClass
mySettings = [[NSMutableArray alloc] init];
[mySettings addObject:[settingsOption optionWithKey:#"age" andDisplayElement:[UIButton buttonWithStyle:UIButtonStyleRect]]];
[mySettings addObject:...];
}
The table's cellForRowAtIndexPath would just do
[cell addSubview:[[mySettings objectAtIndex:indexPath.row] displayElement]];
You were talking about sections, though, which would add another layer to the data. This might simply be a matter of splitting mySettings into an array of arrays instead, where each array in the array is one section.
Not sure if I missed anything above. Feel free to point and poke.
You might simplify the settingsOption class further by adding more helper classes for various types of elements, e.g.
+ (settingsOption *)buttonWithKey:(NSString *)key;
+ (settingsOption *)switchWithKey:(NSString *)key;
+ (settingsOption *)pickerWithKey:(NSString *)key withDataSource:(id <UIPickerViewDataSource>)source withDelegate:(id <UIPickerViewDelegate>)delegate;
etc etc.

Re-usable Obj-C classes with custom values: The right way

I'm trying to reuse a group of Obj-C clases between iPhone applications. The values that differ from app to app have been isolated and I'm trying to figure out the best way to apply these custom values to the classes on an app-to-app basis.
Should I hold them in code?
// I might have 10 customizable values for each class, that's a long signature!
CarController *controller = [[CarController alloc] initWithFontName:#"Vroom" engine:#"Diesel" color:#"Red" number:11];
Should I store them in a big settings.plist?
// Wasteful! I sometimes only use 2-3 of 50 settings!
AllMyAppSettings *settings = [[AllMyAppSettings alloc] initFromDisk:#"settings.plist"];
CarController *controller = [[CarController alloc] initWithSettings:settings];
[settings release];
Should I have little, optional n_settings.plists for each class?
// Sometimes I customize
CarControllerSettings *carSettings = [[CarControllerSettings alloc] initFromDisk:#"car_settings.plist"];
CarController *controller = [[CarController alloc] initWithSettings:carSettings];
[carSettings release];
// Sometimes I don't, and CarController falls back to internally stored, reasonable defaults.
CarController *controller = [[CarController alloc] initWithSettings:nil];
Or is there an OO solution that I'm not thinking of at all that would be better?
I would personally consider some kind of "settings" class, in the tradition of Objective-C data sources. Set up a class that's responsible for being a "data source" for each individual app: have it provide either a set of methods for the values you need for that particular app, or a single "getValueForKey"-style method that returns the appropriate value.
Either way, the solution:
Keeps values in code
Removes the overhead of one massive plist for every setting, some of which may not be used
Removes the work of splitting out bunches of tiny plist files
Allows you to have other classes call your data source wherever they need it
Gives you the flexibility of OO (you could, in theory, subclass your data source should the situation or need arise)
Localizes the changes you need to make; just include the data source class as part of the set of classes you're reusing from app to app, and tweak just that class as appropriate (rather than having to change things throughout other classes)
Lets you set reasonable defaults - or even throw exceptions - for data values you don't expect to be needed in a particular application, without the need for explicit fallback code in every calling class (write the default or exception once in the data source class)
I would give a delegate member to each class to ask what the fine details should be. What font should I use? I'll go ask my delegate. It does not have to be the same delegate for each class or instance, but it can be. For values that can have defaults, use respondsToSelector: to allow for optional delegate methods.
You could make the delegate go look in an xml/plist file for the details, in which case you have it all in one place and can even download a new file to change little details.
All your classes can have the same initWithDelegate: method, which makes it easier to instantiate one without knowing what it is.
Prariedogg,
Absolutely externalize the settings into something like a plist. You can even have multiple plists that align to the particular situation (e.g. settingsMac.plist, settingsIPhone.plist).
When your app determines what environment it is running in, it loads the appropriate settings via a delegate or a central singleton.
By externalizing it you will reduce the average maintenance cost of the application. There is less cost to managing and deploying plist updates than recompile/retest/repackage/redeploy.
-- Frank

Objective C Object Functioning & Passing Arrays

I apologise if this has been asked before but I can't find the info I need.
Basically I want a UITableView to be populated using info from a server, similar to the SeismicXML example. I have the parser as a separate object, is it correct to alloc, init an instance of that parser & then tell RootViewController to make it's table data source a copy of the parser's array.
I can't include code because I haven't written anything yet, I'm just trying to get the design right before I start. Perhaps something like:
xmlParser = [[XMLParser alloc] init];
[xmlParser getXMLData];
// Assuming xmlParser stores results in an array called returnedArray
self.tableDataSource = xmlParser.returnedArray
Is this the best way of doing it?
No, you don't want to do this. You don't want your view controller directly accessing the array of the data-model. This would work in the technical sense but it would be fragile and likely to fail as the project scaled.
As the projects grow in complexity, you will want to increasingly wrap your data model object (in this case the xmlParser) in protective layers of methods to control and verify how the data model changes. Eventually, you will have projects with multiple views, multiple view controllers as well as information entering from both the user and URLs. You need to get into the habit of using the data-model object not just a dumb store you dump stuff into but as an active manager and verifier of your data.
In a situation like this I would have my data-model's array completely wrapped by making it a #protected or #private property. Then I would have dedicated methods for fetching or inserting data into the actual array inside the data-model class itself. No objects outside of the data-model should actually have direct access to the array or have knowledge of its indexes.
So, in this case your data-model would have something like:
- (NSString *) textForLineAtIndexPath:(NSIndexPath *) anIndexPath{
//... do bounds checking for the index
NSString *returnString=[self.privateArray objectAtIndex:anIndexPath.row];
if (returnString=='sometest'){
return returnString;
}
return #""; //return an empty string so the reciever won't nil out and crash
}
as well as a setTextForLineAtPath: method for setting the line if you need that.
The general instructional materials do not spend enough (usually none) time talking about the data-model but the data-model is actually the core of the program. It is where the actual logic of the application resides and therefore it should be one of the most complex and thoroughly tested class in your project.
A good data-model should be interface agnostic i.e. it should work with a view based interface, a web based interface or even the command line. It should neither know nor care that its data will be displayed in a tableview or any other interface element or type.
When I start a new project, the first thing I do is comment out the '[window makeKeyAndVisible];' in the app delegate. Then I create my data-model class and test it old-school by loading data and logging the outputs. Only when it works exactly how I wish it to do I then proceed to the user interface.
So, think real hard about what you want the app to do on an abstract level. Encode that logic in a custom class. Isolate the data from all direct manipulation from any other object. Verify all inputs to the data before committing.
It sounds like a lot of work and it is. It feels like overkill for a small project and in many cases it is. However, getting the habit early will pay big dividends very quickly as your apps grow in complexity.
Not quite. You want the data source to be an object that implements the UITableViewDataSource protocol; what I would do in this situation is create an object that implements that protocol and parses XML, so that you can alloc-init it, then set the data source to that object and have it update the table view as appropriate. So based off your code (and assuming you're running within the table view's controller):
XMLParserAndDataSource xpads = [[XMLParserAndDataSource alloc] init];
[xpads getXMLData];
self.tableView.dataSource = xpads;
It's probably a good idea to give this class itself a reference to an NSXMLParser object, so you can use that to parse the XML, then provide convenience methods (like getXMLData) as well as the UITableViewDataSource methods for your own use. (If you go this route, you should also make your XMLParserAndDataSource class implement the more useful of the NSXMLParser delegate methods, and use them as appropriate to update your table view.)
I'm a Mac programmer and not an iPhone programmer; but on the mac,
self.tableDataSource = xmlParser.returnedArray is not correct. You are supposed to either bind the table's content to an Array Controller (if iPhone has one?) or set the datasource outlet to your RootViewController.
In your rootview controller, you would implement the methods:
– tableView:cellForRowAtIndexPath:
– tableView:numberOfRowsInSection:
For – tableView:cellForRowAtIndexPath: you would return a UITableViewCell with the data you received from the XML parsing according to the index path like so:
UITableCell *myCell = [UITableCell new];
myCell.textLabel.text = [parsedXMLArray objectAtIndex:[indexPath indexAtPosition:indexPath.length-1]];
return myCell;
(Something people don't know is that you can use the + new class method on all NSObject subclasses which automatically call alloc/init.)
For – tableView:numberOfRowsInSection just return the count of the data array:
return parsedXMLArray.count;
Can't edit my question nor post replies, can only post my response as answer.
#TechZen: I'm somebody who tries to form analogies, helps me understand. What you're saying is something like: My original idea was like going into the file room & dumping all the originals on my desk to work on where as you suggest the object be more like an organised file clerk who will search through the data for me and only return the specific datum that I need while being the only one with direct access to that data.
Have I understood correctly?
#Tim: What if I later need the parser to get data for something which is not a table? That's why I thought to dump it into an array & let the caller decide what to do with the data. Would you suggest a second object that would supply the data in the newly required form? (Am I sort of one the right track here or way off?)