I have an iPhone iOS4.1 application that uses localized strings. I have just started building unit tests using the SenTestingKit. I have been able to successfully test many different types of values.
I am unable to correctly test any of my code that uses NSLocalizedString calls, because when the code runs in my LogicTests target, all of my NSLocalizedString calls only return the string key.
I have added my Localizable.strings file to the LogicTests target.
My question is: How must I configure my LogicTests target so that calls to NSLocalizedString will return the localized string and not the string key.
This problem was driving me crazy, but I was able to get NSLocalizedString to behave.
zoul was right, if you print the mainBundle to the console in a logic test, it's not the same bundle that contains your Localizable.strings file. You need to conditionally redefine NSLocalizedString whenever you run your unit tests. I did it in the following steps:
We need a way to tell when we're in our logic tests target, so add something like LOGIC_TESTS to your logic tests target's Preprocessor Macros build setting.
There's only 1 place in my code where I need to redefine NSLocalizedString, so I was able to place the following code in the header corresponding to that class. If you're having this problem in multiple places, I'd suggest putting the following code in a header and #include-ing it where you need it (I tried using a .pch file but it doesn't work in Logic Tests). Anyway, place this somewhere in the header of class(es) that use NSLocalizedString:
#ifdef LOGIC_TESTS
#undef NSLocalizedString
#define NSLocalizedString(key, comment) [[NSBundle bundleWithIdentifier:#"YOUR_IDENTIFIER"] localizedStringForKey:(key) value:#"" table:nil]
#endif
Replace YOUR_IDENTIFIER with the Bundle Identifier of your app's bundle (found in your Info.plist file, key is CFBundleIdentifier). This assumes that you've defined LOGIC_TESTS as a preprocessor macro only in your Logic Tests target.
edit: Curiously, once I removed some debugging code this solution stopped working. It looks like you have to trick Xcode into loading the bundle as well. The following does it:
NSString *path = #"path_to_main_bundle";
NSBundle *bundle = [NSBundle bundleWithPath:path];
NSLog(#"bundles: %#", [NSBundle allBundles]);
Where path_to_main_bundle is == [[NSBundle mainBundle] bundlePath] when you run your main target. Just log it once in gdb or using NSLog on your app delegate to grab the path. It should look something like /Users/YOUR_USER_NAME/Library/Application Support/iPhone Simulator/4.1/Applications/UUID_LOTS_OF_LETTERS_AND_NUMBERS_HERE/App.app.
I placed that code in the setUp call for one of my logic test classes. And no, I have no idea why I have to log allBundles to make it work, so anyone who has a clue, please let me know!
I was able to use NSLocalizedString using the following code in the setup for my unit test
- (void)setUp
{
[super setUp];
NSString *bundlePath = [[NSBundle bundleForClass:[self class]] resourcePath];
[NSBundle bundleWithPath:bundlePath];
}
I encounter the same problem, and, thanks #kevboth, I tackle it by adding two lines to YourUnitTests-Prefix.pch:
#undef NSLocalizedString
#define NSLocalizedString(key, comment) [[NSBundle bundleForClass:[self class]] localizedStringForKey:(key) value:#"" table:nil]
A shortcut for Swift:
This is a simple version which can be extended to different use cases (e.g. with the use of tableNames).
public func NSLocalizedString(key: String, referenceClass:AnyClass) -> String
{
let bundle = NSBundle(forClass: referenceClass)
return NSLocalizedString(key, tableName:nil, bundle: bundle, comment: "")
}
This global method can be placed in a single .swift file or somewhere else outside the class scope.
Use it like this:
NSLocalizedString("YOUR-KEY", referenceClass: self)
Maybe NSLocalizedString will only work inside the application tests? This is a macro that invokes localizedStringForKey:value:table: on the main bundle. Maybe +[NSBundle mainBundle] returns something iffy in the testing target?
The cleanest solution seems to me just to include a Localizable.strings in your octest bundle.
Swift 4.1 - Xcode 9.3
Localizable.strings (en)
"TEXT_TO_LOCALIZE" = "Hello!";
Localizable.strings (es)
"TEXT_TO_LOCALIZE" = "Hola!";
XCTestCase
class AppTestCase: XCTestCase {
func testLocalize() {
let localizedText = "TEXT_TO_LOCALIZE".localized(for: reference)
print(localizedText) // "Hello!" or "Hola!"
}
}
extension String {
public func localized(for aClass: AnyClass) -> String {
let bundle = Bundle(for: aClass)
return NSLocalizedString(self, tableName: nil, bundle: bundle, comment: "")
}
}
extension XCTestCase {
var reference: AnyClass {
return type(of: self)
}
}
Related
We are using the below code to get reference to the bundle we add for our resources like xib and images however I am not sure what would be the code to access them in swift.
NSString *resourceBundlePath = [[NSBundle mainBundle] pathForResource:#"DTPinLockController" ofType:#"bundle"];
NSBundle *resourceBundle = [NSBundle bundleWithPath:resourceBundlePath];
Can anyone convert this to swift 1.2 code?
Constructors in Swift are a little different than Objective-C. Try this:
let path = NSBundle.mainBundle().pathForResource("DTPinLockController", ofType: "bundle")!
let bundle = NSBundle(path: path)
And for loading a UIViewController afterwards in a NIB inside that bundle:
let controller = UIViewController(nibName: "MyViewController", bundle: bundle) as! MyViewController
Take a look at the Apple documentation for the full reference. There's a selector at top that lets you switch between Swift and Objective-C.
I am setting the value for the string in the viewdidload method and getting the string value in the button action method the app gets crashed. can I know the reason for crashing and how to pass the values to the method.
in .h file
NSString *test;
in .m file
-(void)viewDidLoad
{
test = [NSString stringWithFormat:#"sample"];
}
-(IBAction) buttonPressed:(id)sender
{
NSLog(#"%#", test);
}
When I pressed the button the app crashes.
Please try using this solution, I think this will help you,
Create Property of test in .h file like this,,
#property(nonatomic,retain) NSString *test;
and synthesize it in .m file like this,
#synthesize test;
now use test as self.test in .m file like this,
-(void)viewDidLoad
{
self.test = [NSString stringWithFormat:#"sample"];
}
-(IBAction) buttonPressed:(id)sender
{
NSLog(#"%#", self.test);
}
Another solution for this is just retain that test string in ViewDidLoad also, I think this will also help you..
Hope this will help you..
Let me try to explain it in more detail:
You have a string variable in .h file. In view did load you are assigning it as:
test = [NSString stringWithFormat:#"sample"];
What actually happning in this code is your test is a autoreleased object. When you use this and object without alloc and init this is autoreleased object and will release memory after the method you occupied it.
For avoiding this situation you can use #Mehul's solution by creating property. This is against encapsulation concept. Sometimes you have objects you don't want to access outside of the class or don't want to show with objects. Use following in those conditions:
test = [[NSString stringWithFormat:#"sample"] retain]; // or
test = [[NSString alloc] initWithFormat:#"sample"];
this will keep your string alive till you release it.
There is another way that is not good to use but want to tell you so you can understand it better. Using
test = #"sample";
If you don't want to append string or use it with format you can assign simple string to you NSString object.
using this will have a infinite retainCount of your test variable. You can use this to avoid crash but this is not preferable because as I told this have a infinite retaiCount you can't release it and free your memory after use. So earlier methods are more correct.
This is true with all of your autorelease objects which are created with class methods and not with init.
Hope this will clear you more.
I think simple allocation will solve the problem. Just replace this code in the viewDidLoad method
-(void)viewDidLoad {
test=[[NSString alloc]initWithString:#"Sample"];
}
In some of my screens in my program the localized strings work and in some it doesn't.
(Xcode 4.2)
What I've done:
Added a Localizable.strings in the folder "Resources"
in my .h file, inside the #interface ClassViewController : UIViewController {} I added:
IBOutlet UILable *labelName;
Also in the .h file, I added
property (nonatomic, assign) IBOutlet UILabel *labelName;
In the .m file, I added :
#synthetize labelName;
Still in the .m file, I added inside "-(foid)dealloc" :
[labelName release];
In -(void)viewDidLoad I added :
self.labelName.text = [NSString stringWithFormat:#"KEY"];
Finally, in the xib file (with the Interface manager), I linked the label object with the variable.
So, as I said, this method works in some screen and not in others. Any idea?
Solution:
That what a stupid mistake. the line to enter the text should be:
NSLocalizedString(#"KEY", nil);
Use instead:
self.labelName.text = NSLocalizedString(#"KEY", "");
From the Documentation:
Name: NSLocalizedString
Description: NSString *NSLocalizedString(NSString *key, NSString *comment)
Availability: iOS (2.0 and later)
Abstract: Returns a localized version of a string.
As Vince pointed out, you need to use NSLocalizedString function. So in this case the code in 6 would be:
self.labelName.text = NSLocalizedString(#"The key for this label",#"Some comment");
That should work for you.
In XCode-4.2 you can do the following:
Click on "target"
Then select "Build Phase"
Select "Add Build Phase" & choose "Add Copy files".
After select "copy files(int item)".( where 'int' may be 1or 2 or 3 etc. depending upon how many items previously have been created).Set "Destination" to "Resources".
Now click on '+' & choose your created '.string' file(eg. localization.string).
It works, hope it will help you.
What do you imagine this line does?
self.labelName.text = [NSString stringWithFormat:#"KEY"];
Because it's functionally equivalent to writing this:
self.labelName.text = #"KEY";
But I suspect that what you meant to write was this instead:
self.labelName.text = NSLocalizedString(#"KEY", #"description");
I am new to iPhone development. I want to access a string variable in all the class methods, and I want to access that string globally. How can I do this?
Please help me out.
Leaving aside the issue of global variables and if they are good coding practice...
Create your string outside of any Objective-C class in a .m file in your project:
NSString *myGlobalString = #"foo";
Then put the declaration in a header file that is included by every other file that wants to access your string:
extern NSString *myGlobalString;
OK, well I can't leave it entirely aside. Have you considered putting your "global" string somewhere else, perhaps inside your application delegate as a (possibly read-only) property?
The preferred methods for creating a global variable are:
Create a singleton class that stores
the variables as an attributes.
Create a class that has class methods that return the variables.
Put the class interface in the
universal header so all classes in
the project inherit it.
The big advantage of method (2) is that it is encapsulated and portable. Need to use classes that use the global in another project? Just move the class with the variables along with them.
You can achieve that by implementing getter and setters in the delegate class.
In delegate .h file
Include UIApplication delegate
#interface DevAppDelegate : NSObject <UIApplicationDelegate>
NSString * currentTitle;
- (void) setCurrentTitle:(NSString *) currentTitle;
- (NSString *) getCurrentTitle;
In Delegate implementation class .m
-(void) setCurrentLink:(NSString *) storydata{
currentLink = storydata;
}
-(NSString *) getCurrentLink{
if ( currentLink == nil ) {
currentLink = #"Display StoryLink";
}
return currentLink;
}
So the variable you to assess is set in the currentlink string by setters method and class where you want the string ,just use the getter method.
All the best
I posted an article about my methodology for doing this:
http://www.pushplay.net/2011/02/quick-tip-global-defines/
This is something I primarily use for notification keys. Creating a globals.h file and adding it to the (your_project_name)_Prefix.pch file will ensure it is accessible globally...
The UI of my iPhone application has lots of static labels, and I have set accessibility hints for them in Interface Builder. I want to access these programmatically so I can provide help bubbles - a custom subclass of UILabel recognises a touch and displays a bubble with the value of [self accessibilityHint].
However, [self accessibilityHint] returns nil. If I set the value programmatically [self setAccessibilityHint: #"Hello"] then I can access that value from my program, but the initial value from the NIB isn't available.
If I turn on the Accessibility Inspector before launching my application, the hints from the NIB files are available via the accessibilityHint property. Is there some flag somewhere that determines whether the system loads these properties; and if there is, is there some way I can set it?
My backup option is to have my controllers keep references to each UI label and set the accessibilityHint in code, but that's pretty ugly and cumbersome.
Hmm, if I open Library/Preferences/com.apple.Accessibility.plist and change ApplicationAccessibilityEnabled from false to true, then it works. (That path being in ~/Library/Application Support/iPhone Simulator/)
I tried adding this at the start of main():
CFPreferencesSetAppValue(#"AccessibilityEnabled", kCFBooleanTrue, #"com.apple.Accessibility");
CFPreferencesSetAppValue(#"ApplicationAccessibilityEnabled", kCFBooleanTrue, #"com.apple.Accessibility");
CFPreferencesAppSynchronize(#"com.apple.Accessibility");
but it didn't work. (It wrote a file to Applications/{UUID}/Library/Preferences/com.apple.Accessibility.plist)
EDIT: After stepping through the UIKit code, the call that determines whether accessibility is turned on or off is
CFPreferencesGetBooleanValue(#"ApplicationAccessibilityEnabled", #"/Users/sam/Library/Application Support/iPhone Simulator/User/Library/Preferences/com.apple.Accessibility", NULL);
Note the bizarre app key, I'm not yet sure where this value comes from (my knowledge of 386 assembly is very weak!) but I can pass this key to CFPreferencesSetAppValue and it works, at least on the simulator (I don't have access to an actual device at the moment).
Also this will turn on app accessibility for all applications (since it writes it to the global plist). I can set a flag from main() if the value should be set back to false once the application has launched.
This works on simulator and device. Taken from http://sgleadow.github.com/blog/2011/11/16/enabling-accessibility-programatically-on-ios-devices/
#import <dlfcn.h>
NSAutoreleasePool *autoreleasePool = [[NSAutoreleasePool alloc] init];
NSString *appSupportLocation = #"/System/Library/PrivateFrameworks/AppSupport.framework/AppSupport";
NSDictionary *environment = [[NSProcessInfo processInfo] environment];
NSString *simulatorRoot = [environment objectForKey:#"IPHONE_SIMULATOR_ROOT"];
if (simulatorRoot) {
appSupportLocation = [simulatorRoot stringByAppendingString:appSupportLocation];
}
void *appSupportLibrary = dlopen([appSupportLocation fileSystemRepresentation], RTLD_LAZY);
CFStringRef (*copySharedResourcesPreferencesDomainForDomain)(CFStringRef domain) = dlsym(appSupportLibrary, "CPCopySharedResourcesPreferencesDomainForDomain");
if (copySharedResourcesPreferencesDomainForDomain) {
CFStringRef accessibilityDomain = copySharedResourcesPreferencesDomainForDomain(CFSTR("com.apple.Accessibility"));
if (accessibilityDomain) {
CFPreferencesSetValue(CFSTR("ApplicationAccessibilityEnabled"), kCFBooleanTrue, accessibilityDomain, kCFPreferencesAnyUser, kCFPreferencesAnyHost);
CFRelease(accessibilityDomain);
}
}
[autoreleasePool drain];