A simple way to make the code more readable for localizedStrings in Xcode - ios

I am mostly writing this for other newbie folks and to remind myself in a few years when I forget what I did
I had a long list of localized strings in the application I was building. I really hated the lack of readability as I plodded my way through a whole bunch of statements like
[_myUILabelObject setText:NSLocalizedStringFromTable(_myString, applicationLanguage,nil)]
Then I was going to need another one that was different for UIButtons and UITextView and so on. There had to be a better way. What I wanted was something like
[_myUILabelObject translateMe:#"This text");

So here's what I did:
I created a bunch of class extensions (technically categories) for each object: UIButton, UILabel, UITextView... whatever I needed.
Each one has a simple .h file that simply declares that the extension is related to the main class. Here's an example for my translateMe for UITextField:
#interface UITextField (translateMe)
- (void) translateMe: (NSString *) usingString;
#end
The first line tells the compiler that translateMe is going to be an extension of UITextField.
The second line tells the compiler that translateMe is expecting to be a method of UITextField that receives an NSString that I've called usingString.
Then in the .m implementation file, I've done this:
extern NSString *applicationLanguage;
- (void) translateMe: (NSString *) string {
[self setText:NSLocalizedStringFromTable(string, applicationLanguage,nil)];
[self sizeToFit];
}
The applicationLanguage string is a string that I set elsewhere based on the user's selection of language and it essentially is the name of the .strings file.
The rest of the code here simply includes all the laborious coding that I wanted to avoid in my ViewController's code.
Now the magic here is that one can create similar combinations of the .h and .m files for every object you have in your ViewControllers. If you call all of the methods translateMe, then you don't have to worry at coding time which of the objects you're actually calling. The result is you get something that looks simple like this:
[_standardDemoPlanTitle translateMe: #"Standard Demo Plan"];
[_linkSampleDemoVideoButton translateMe: #"Link to sample demo"];
[_linkCustomerPresentationButton translateMe: #"Link to customer presentation"];
[_personalNotesTitle translateMe: #"Your Personal Notes"];
[_theSaveButton translateMe: #"Save"];
[_emailPlanButton translateMe: #"Email this plan to a colleague"];
Note that the fact that some are buttons and some are labels (Titles) doesn't matter at all.
As an additional short cut, I learned from another post that it is possible to deal with long sections of text in a short way too.
Instead of:
[_longTextRequiredByLawyers translateMe: #"blah blah blah blah blah forever..."];
What I can do is to put in a short handle for the text in the implementation file and put the longer text in the .strings file. Here's an example:
[longTextRequiredByLawyers translateMe: #"#lawyerText"];
by the way, the #-sign is not required, but I used it throughout my .m files as a personal reminder so that I knew that what was going to inserted did not necessarily match with what was in the prototype in the interface builder.
Then my english.strings file included something like this:
"#lawyerText" = "When in the course of human events it becomes necessary for one people to dissolve the bonds which have connected them with another and to assume among the powers of the earth the separate and equal station to which the laws of God...";
and my spanish.strings file had the translation:
"#lawyerText" = "En México no hay abogados, entonces el texto es mas corto.";
You could do the same thing for Albanian and Klingon.
So, key tricks (or good programming ideas here):
Use Cateogries to extend the class definitions for the UI objects you have on your pages. Those categories can include everything you need to make your main code more readable.
Name the method the same thing in every extension so that you don't have to think about it when you do the magic in your .m files.
Use short handles for long strings of text and jam the longer stuff into the .strings files.
One thing I wish I had figured out:
I now have a long list of .h files that I now have to #include at the top of all of my ViewController .m files. I wish that I could have a .h file that #includes all of the translateMe .h files... kind of a nested .h.
UIButton+translateMe.h
UITextView+translateMe.h
UILabel+translateMe.h
UIButton+translateMe.h
and so on
Wouldn't it be nice to have a single .h file, perhaps allMyTranslates.h that has the list above inside of it, and then I just include allMyTranslates.h in the code I am writing.
I would end up replacing:
#include "UIButton+translateMe.h"
#include "UITextView+translateMe.h"
#include "UILabel+translateMe.h"
#include "UIButton+translateMe.h"
#include and so on
with this
#include "allMyTranslateHeaders.h"
Well, there you go... a few notes from a newbie... All the experts have been helpful of course, but every once in a while we need a note like this one to get us over the hump!

Related

NSString Subclass or Wrapper Class or Category

I'm currently helping a client that needs to change the language in their app due to certain governmental guidelines (medical vs wellness wording). Their app is huge and all the strings are contained in the code i.e. (stringWithFormat/hardcoded), none of it is in an external table. Meaning this would be a huge manual task.
At a undetermined point in the future the client believes they will receive approval to return to their current wording and will want to switch the strings back. Most of the changes will literally be switching a single problematic word to a less problematic word.
I thought that maybe if I could change the strings at run time based on a bool switch that it might eliminate the manual work involved and it would let me switch the language back when needed.
First attempt:
+ (instancetype)stringWithFormat:(NSString *)format, ...
{
va_list args;
va_start(args,format);
//todo check flag if we're changing the language
//todo replace problematic word from 'format'
NSString *result = [NSString stringWithFormat:format,args];
return result;
}
I first quickly coded up a category to override stringWithFormat to replace problematic words. I forgot that I would lose the original implementation of stringWithFormat. This resulted in endless recursion.
Next Attempt (subclass):
I started an attempt to subclass NSString but hit a stackoverflow post saying that if my solution was to subclass a class cluster then I didn't understand my problem because subclassing a class cluster is almost never done.
Final Option (wrapper):
My final attempt would be to write a wrapper class but that kind of defeats the original purpose which was to avoid having to manually seek out each string in the app.
I'm not really sure how to approach this problem anymore. What do I do if I need to add/override functionality to one of the core classes.
There is nothing wrong with the idea of your first attempt, just a little mistake in its implementation. Change:
NSString *result = [NSString stringWithFormat:format,args];
to:
NSString *result = [NSString alloc] initWithFormat:format arguments:args];
which is the expansion of stringWithFormat: and the interception will work.
Thoughts about class clusters are a red herring in this particular situation, the front class for a cluster (NSString) must provide implementations for class methods (+stringWithFormat:), so you can use a simple category to intercept them.
However, having intercepted +stringWithFormat: be careful. A simple test will show you it is used a lot by the frameworks and you do not wish to break them - as my first simple test did by simply changing "d" to "c", which changes "window" to "wincow", which in turn broke the binding setup of Xcode's default app which binds the property "window"...
If you are changing health-related words you might be OK, whole strings would be better.
A better approach might be to simply write a RE to match all the literal strings in the code and replace them by function(string) for some function (not method) you write to do the conversion.
HTH
There is a much simpler solution that seems like a better fit. Use NSLocalizedString, with keys instead of actual strings:
displayString *NSString = NSLocalizedString(#"displayString", nil);
cancelButtonTitle *NSString = NSLocalizedString(#"cancelButtonTitle", nil);
Then create a Localizable.strings file in your app, and define the actual values that should be displayed:
"displayString" = "The string to display in English"
"cancelButtonTitle" = "Cancel"
You put the Localizable.strings file in your app bundle and the app uses it to do string replacements at runtime.
You can also define different versions of Localizable.strings for different languages, so, for example, if the user has set their language to Spanish, the call to NSLocalizedString() gives you the Spanish-language version.
(As you've mentioned, NSString is a class cluster. That means that it is a public interface to a variety of different private subclasses. You can't be sure what private subclass you get when you create an NSString, and that makes it a bad idea to try to subclass it.)
For hardcoded strings you have no other way but to modify those manually by assigning it to a string converter class of some sort. So those for:
yourmom.text = #"Hi Mom";
yourdad.text = [NSString stringWithFormat:#"%# and Dad!",yourmom.text];
You need to change these kind of assignments to something like
yourmom.text = [StringConverter string:#"Hi Mom"];
yourdad.text = [StringConverter string:#"%# and Dad!" placeHolder:yourmom.text];
As for strings in storyboards or xibs, you can change them by iterations loop in viewdidload. Good luck.

Any smarter way to define constants in bulk?

I want to have macros for all constant strings in the project, which I am assigned to maintain.
The format of database fields are like #"first_name", #"last_name", etc.
What I want is like follows:
#define kFirstName #"first_name"
#define kLastName #"last_name" // And so on.
Problem: I have over 3500 unique fields with constant strings. I want each one of them to be defined as macro.
Any smarter way to do this. I am thinking of manually copy, paste & edit each one of them.
Tagging as iOS & Objective-C, as the project itself is an iPad Application.
In general, defining constants like this is the way to go on iOS, so you're on the right track.
You surely won't get around typing out each of the fields at least once.
I would recommend either of two approaches:
use multiple .h-files for the definition of all the constants. you can group the header files according to the definitions that they contain (e.g. all fields related to user data in UserDefinitions.h). that way you at least make sure that you don't have to import all the constants everywhere in your code. working with prefixes will also be helpful in this situation, so prefix all the Macros names with the .h-file that they contain, e.g. kUserFirstName so that you you know at first sight where this constant comes from
define all your constants in one (or multiple) property lists. that makes them easy to maintain. only make sure that you don't load the property file each time you use one of the constants, but rather cache the file once its loaded for the first time.
When using Core Data consider using mogenerator which creates constant values for you that you can reference for all of the attribute and relationship names.
the cleanest way is to make a pair of constants files (header and main). Create a new class (inheriting from whatever, NSObject say) call it constants. Delete the #interface and #implementation, so you have an empty header (except for #import Foundation/Foundation.h) and empty main (except for importing the header.)
then declare each in the header like this
extern NSString *const kFirstName;
and implement each (in the .m file) just like this
NSString *const kFirstName = #"Johnny";
make sure the .m file is added to your target, import the header where need be.

Should Objective-C constants be wrapped by the implementation block?

I have an iOS application I'm writing. I've moved away from #define to create my constant values. I have a few questions regarding the use of these style declarations: NSString *const segueToMainMenu
If I'm using these internally, I'm placing them inside the .m file. However, should I be placing these wrapped around the #implementation block or outside of it or does it matter? I'm thinking inside, because they are specific to the implementation and not global, but I'm not sure. Any details are appreciated.
If I'm creating a more global scope using the extern keyword and I'm using a Constants file pair (Constants.h/Constants.m) do I need to place those in the #interface section and then define them in the #implementation section? What is the benefit of that vs the old way of just using a Constants.h file and including it with other headers? Why do I now need two files?
Is the standard practice still to name the constants with a "k" prefix (e.g. kAnimationDuration) or should I now be doing something like MainMenuViewControllerAnimationDuration? I'm imagining yes and if so, does it matter for the constants from number 1 (i.e. not extern) how I name them? In other words, are those visible outside of my implementation?
Clarification is much appreciated.
Doesn't matter whether you place them inside the implementation block or not—only methods are part of the class implementation, so the scope of constants won't change regardless.
The k prefix is a bit dated now. The usual way is to name constants as <prefix><name>, such as "MDSomeConstant".

iOS Code Coverage: #implementation Not Hit?

I've recently added test coverage analysis to our codebase, and thankfully the classes that I expected to be well covered are clocking in above 95% coverage. I love the ones that hit 100%, since that's obviously the goal, but a handful of my classes are stuck at 96-98%, because one line isn't being hit. That one line is the #implementation ClassName line, which I find really confusing.
Logically if all of the class's executable lines were exercised, the class was obviously instantiated.
I'm using the standard Xcode methods (__gcov_flush()) for generating .gcda and .gcno usage data, and I'm using CoverStory for the analysis and HTML generation.
It's not a huge deal; obviously the class is well covered, but it's annoying to keep mental notes of the classes that aren't at 100% because the dang #implementation line is missed for whatever reason.
I can't find a pattern between the files that have the #implementation line hit and those that have it missed. Has anyone else experienced this and/or know what I might try to alleviate it? Could it even be a bug with CoverStory perhaps?
I've had the same problem with my current project and you can fix it using:
Edit: For XCText, it should be XCTAssertEqualObjects since NSString * are objects
Kiwi:
[NSStringFromClass([[YourClass new] class]) should] equal:NSStringFromClass[YourClass class])]
or
XCTAssertEqualObjects(NSStringFromClass([[YourClass new] class]), NSStringFromClass([YourClass class]));
#implementation YourClass will hit while an instance of that class going to be dealloc'ed.
You can confirm that by create a empty demo class, put a checkpoint on #implementation DemoClass, and call [DemoClass new], the debugger will stop while [DemoClass .cxx_destruct] is being called, which means the instance has zero retainCount and going to be dealloc'ed.
You can also confirm that by view the code coverage html file created by XcodeCoverage; select a file, click "function" which will list all functions coverage report for that file, and you will see [DemoClass .cxx_destruct] list as a function in either green or red.
In other word, If #implementation YourClass is not being called, there is a good chance that you had retain cycles/static variables in your class implementation that prevent your class from deallocating. (e.g. contains NSURLSession object but forget to call finishTaskAndInvalidate, or contains repeat NSTimer but didn't call invalidate , or it is singleton class which contains static variables etc.)

using #implementation creates "instance method conflict" warnings in XCode

In my iOS application I have a 5 view controllers that all deal with the same feature (groups). These view controllers can be pushed on top of eachother in a few different configurations. I made a file called GroupViewHelper.h which uses #implementation to provide some functions for the groups feature. The functions look through the view controller stack and send a "refresh" message to a view controller of a specific type. The file looks like this:
#implementation UIViewController (GroupViewHelper)
- (void) refreshManageGroupsParent
{
// ...
}
- (void) refreshGroupDetailsParent
{
// ...
}
#end
My code works great and everything behaves as expected, but I get 14 warnings that are all very similar to this at build time:
ld: warning: instance method 'refreshGroupDetailsParent' in category from /Users/x/Library/Developer/Xcode/DerivedData/myapp-ayshzmsyeabbgqbbnbiixjhdmqgs/Build/Intermediates/myapp.build/Debug-iphonesimulator/myapp-dev.build/Objects-normal/i386/GroupMembersController.o conflicts with same method from another category
I think I'm getting this because I'm using a .H which is included in multiple places, but how do I correctly use #implementation in this situation?
I think I'm getting this because I'm using a .H which is included in multiple places
Well, sort of, but the real problem is that you've put the #implementation in the .h file in the first place. If you only included that .h file in one place, you would get away with it—but it would still not be the right way to do it.
but how do I correctly use #implementation in this situation?
Put it in a file called GroupViewHelper.m, and add that file to your project's sources, and put the #interface in GroupViewHelper.h.
Or, ideally, call them UIViewController+GroupViewHelper.m and UIViewController+GroupViewHelper.h, because that's the idiomatic way to name category files. (And if you use Xcode's "New File…" menu item to create a new Objective-C category file, that's what it will give you.)
In other words, interfaces and implementations for categories on existing classes work exactly the same as interfaces and implementations for new classes.
I have encountered exactly this issue. I had imported a reference to a header file, on a .m page. However, it also contained a reference to another header file, which contained a reference to another header file - that also referenced the conflicted header file. So indirectly the same header file was imported twice, causing the error.
In my case, the .m file did not need this reference. I was able to delete it, removing the error. My advice is check the files where you have included a reference to the offending header file, and verify that it actually is required.

Resources