So I have an application with different buttons, labels and some Text Views in storyboard where I entered the text directly in the storyboard. I enabled base localization and added a couple of languages.
This generated storyboards for Base (English) and the other languages with a list of items objectIDs.
I translated everything, and the labels and buttons (ALL OF THEM) work and show in the language I set the device to.
The text fields however keep showing the initial English text no matter which language I set...
Are there any extra steps involved for Text View?
So, I did some research, and it seems that in order for this to work correctly, the text for the UITextView needs to be set programmatically.
Source: Devforums.apple
Quote:
as I understand it, strings such as the text property of a text view/field have to be set in code using NSLocalizedString. The first 1/2 hour of WWDC 2013 video session #219 Making Your App World Ready covers this if you have the time to watch it
So, it seems that the workaround (if you don't want to set the text programmatically) is to convert the strings file into a storyboard before shipping the app. This does seem to work as intended and shows the UITextView properly localized.
EDIT: Found another workaround that allows to keep .strings file.
In - (void)viewDidLoad:
for(UIView* v in self.view.subviews)
{
if([v isKindOfClass:[UITextView class]])
{
UITextView* txv = (UITextView*)v;
NSString *loctxt = [txv.text stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
txv.text = NSLocalizedString(loctxt, #"");
}
}
This produces a Percent Escapes encoded string from whatever is inside the storyboard, like this:
Hello%20World
In your Localizable.strings file, you use the above as the key, and this will produce the localized text in the app at runtime for the selected locale, like this:
"Hello%20World" = "Hallo Welt";
The Percent escaping takes care of all escape characters in the base string.
As my comment on Dmitry's answer cannot be formatted nicely, I repeat this as an answer here. The Swift version of his solution looks like this:
override func viewDidLoad() {
super.viewDidLoad()
for view in self.view.subviews {
if let tv = view as? UITextView, ident = view.restorationIdentifier {
tv.text = NSLocalizedString("\(ident).text", tableName: "Main", comment: "")
}
}
// Do any additional setup after loading the view.
}
(Note that in Swift, NSLocalizedString replaces several Objective-C macros, one of them being NSLocalizedStringFromTable)
P.S.: Unfortunately, in iOS 10 this seems not to work any more. Instead, the call gives back the id that was supplied as first parameter (e.g. "abc-xy-pqr.text"). Any ideas?
If anyone is still interested, I have solved this problem a different way, this will allow you to still use the SAME .Strings file that is generated by Xcode for storyboards.
There are two parts to this solution:
In the .m file for your view add this code:
- (void)viewDidLoad
{
[super viewDidLoad];
for(UIView* view in self.view.subviews)
{
if([view isKindOfClass:[UITextView class]] && view.restorationIdentifier)
{
UITextView* textView = (UITextView*)view;
NSString *textViewName = [NSString stringWithFormat:#"%#.text",textView.restorationIdentifier];
textView.text = NSLocalizedStringFromTable(textViewName, #"Main", nil);
//change this to be the same as the name of your storyboard ^^^
}
}
// Do any additional setup after loading the view.
}
and in your storyboard in the identity inspector copy the "Object ID" to the "Restoration ID" field.
This will apply the new localized text to all of your UITextViews on screen load and will allow you to use the already generated strings files.
I made my own Categories for the components.
For example, a button:
#import <UIKit/UIKit.h>
#interface LocalizedButton : UIButton
#end
#import "LocalizedButton.h"
#implementation LocalizedButton
- (id)initWithCoder:(NSCoder *)aDecoder {
NSLog(#"Loading LocalizedButton: initWithCoder");
if ((self = [super initWithCoder:aDecoder])){
[self localizeButton];
}
return self;
}
- (id)initWithFrame:(CGRect)frame{
NSLog(#"Loading LocalizedButton: initWithFrame");
self = [super initWithFrame:frame];
if (self) {
[self localizeButton];
}
return self;
}
-(void) localizeButton{
self.titleLabel.adjustsFontSizeToFitWidth = YES;
NSString* text = NSLocalizedString(self.titleLabel.text, nil);
[self setTitle:text forState:UIControlStateNormal];
[self setTitle:text forState:UIControlStateHighlighted];
[self setTitle:text forState:UIControlStateDisabled];
[self setTitle:text forState:UIControlStateSelected];
}
#end
You can se the complete code on: https://github.com/exmo/equizmo-ios/blob/master/Quiz/LocalizedButton.m
The Swift solution of https://stackoverflow.com/users/1950945/stefan works for me on iOS 10.2 when I replace the "Main" with the correct id (e.g. "MainStoryboard") which references the localized file id.storyboard (e.g. "MainStoryboard.storyboard")
Related
I have two views in my app and a plist file to store some values.
In the first view I've created a button called frequenciesButton that opens the second view and another button to restore the default values.
In the second view there is a pickerView and a "Done" button.
On the .m of the first view:
- (void)viewDidLoad {
[super viewDidLoad];
//
self.gameSettings = [[NSMutableDictionary alloc] initWithContentsOfFile:gameSettingsFilePath];
}
-(void)viewWillAppear:(BOOL)animated {
[self refreshView];
}
- (void)refreshView {
[self.frequenciesButton setTitle:[NSString stringWithFormat:#"%# hz and %# hz", [self.gameSettings objectForKey:#"freq-freq1"], [self.gameSettings objectForKey:#"freq-freq2"]] forState:UIControlStateNormal];
...
}
- (IBAction)setDefaultValues:(UIButton *)sender {
[self.gameSettings setValue:#880 forKey:#"freq-freq1"];
[self.gameSettings setValue:#1122 forKey:#"freq-freq2"];
...
[self.gameSettings writeToFile:gameSettingsFilePath atomically:YES];
[self refreshView];
}
When the first view is loaded, the button title is changed to the default values stored in the gameSettings dictionary. The method setTitle: works.
When I click on the frequenciesButton it opens the second view with the pickerView, I select the two new values for the freq-freq1 and freq-freq2 and it saves to the plist file on done button.
The problem is that the frequenciesButton title is not changed when the second view is dissmissed and the first view appears. The refreshView method is called but the button setTitle: does not work.
In this case, if I go back one screen, and return to this view, the button title is updated.
And when I click on defaultValuesButton, the frequenciesButton title changes. The method setTitle: also works.
Any ideas of what must be happening?
HaHA! I love that you added a link to your project.
SO!! The problem was that you have separate properties in each view to hold the data from the saved plist file, self.settings. This is fine, don't mesh them together. The requirement you had to do with this, when switching views, is to keep the ivar or properties updated as the data updates too :D
Here is how I fixed the problem:
- (void)viewWillAppear:(BOOL)animated {
self.settings = [NSMutableDictionary dictionaryWithContentsOfFile: filePath];
[self updateView];
}
I checked out the file and that was updated, but the dictionary in the TestViewController.h was not updated
I hope this was the problem :)
One problem there, not sure if it will fix it, is the fact that you have used ViewWillAppear incorrectly, you have this:
-(void)viewWillAppear:(BOOL)animated {
[self refreshView];
}
but it should be this:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self refreshView];
}
You need to invoke "[super viewWillAppear:animated];" or you will have side effects, fix that first and see what happens.
I have a UIControl that loads it's UI from a xib file. I've updated it to be IB_DESIGNABLE. One of the things I've added in the ability to loads it's UI when the component is used in another xib or storyboard.
#if TARGET_INTERFACE_BUILDER
-(instancetype) initWithFrame:(CGRect)frame {
self = [self loadFromXib];
return self;
}
#endif
-(id) awakeAfterUsingCoder:(NSCoder *)aDecoder {
if ([self.subviews count] == 0) {
return [self loadFromXib];
}
return self;
}
-(id) loadFromXib {
UIControl *realControl = nil;
Class thisClass = [self class];
for (id obj in [[NSBundle bundleForClass:thisClass] loadNibNamed:NSStringFromClass(thisClass)
owner:nil // This must be nil or we get into a loop.
options:nil]) {
if ([obj isKindOfClass:thisClass]) {
realControl = obj;
break;
}
}
NSAssert(realControl != nil, #"Failed to find an object of type %# in the xib file.", NSStringFromClass(thisClass));
// copy control frame and settings here ....
return realControl;
}
This works well, loading the UI when viewing the storyboard and running the app.
However when I go to edit the xib for the control itself, IB activates this code and draws the control under itself in IB. Thus I see it twice like this:
In this case you can see the label to the right which is the UILabel subview of the control, and the word 'Label' drawn underneath the UIImage which is where it would be if there was no image. This is where the IB preview is loading and drawing the control.
From what i've been able to figure out so far there are two options with getting previews to occur. Firstly when running the app, and secondly when the control is being displayed in IB.
Here I'm looking for a third option which is the ability to say, draw in IB, but not when editing the control itself, only when rendering the control when it's being used.
Has anyone figured out how to do this?
I have a row of labels that have been programmatically instantiated, they are stored in an NSMutableArray. They don't currently contain any data. What I'm trying to do is make it so that when a user types in a character it is automatically displayed in the labels. I'm not sure how to do this. I know how to access the labels I have created [MyArray ObjectAtIndex:0] and so on, but how could I make it so that when a user types on the keyboard it formats the text (I have code for formatting) and then just appears on screen.
I need help putting each character on the screen as it is typed.
Any help would be greatly appreciated - I have a textfield (it's hidden) and the keyboard comes up by button. If that helps. :)
Thank you in advance :).
UITextField *tf;
[tf addTarget:self action:#selector(editingChanged:) forControlEvents:UIControlEventEditingChanged];
- (void)editingChanged:(UITextField *)textField {
_myHiddenLabel.text = textField.text;
}
You can add observer when Text inside UITextField changes and then access your labels and add text to it...
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(changeLabelsMethod:)
name:UITextFieldTextDidChangeNotification
object:myHiddenTextField];
}
-(void)changeLabelsMethod:(UITextField*)txtField
{
Static int i=0;
if(i<[MyArray count])
{
UILabel *lbl=[MyArray ObjectAtIndex:i];
lbl.text=txtField.text;
}
else
return
i++;
}
EDIT: Refer Eugene's answer for right approach
You can get notified every time a character is typed if you set your viewController as delegate to your hidden textField and implementing UITextFieldDelegate Protocol
- (IBAction)textFieldValueChanged {
NSString *strLastChar = [txtSearch.text substringFromIndex:txtSearch.text.length-1];
UILabel *lblCurrent = [arrSearch objectAtIndex:intCurrentLblNo];
[lblCurrent setText:strLastChar];
intCurrentLblNo++;
}
take intCurrentLblNo as global variable and set intCurrentLblNo = 0; in viewdidload method
and set it...
[txtSearch addTarget:self action:#selector(textFieldValueChanged) forControlEvents:UIControlEventValueChanged];
in viewdidload method
Right now, our code is set to grab the text from the UITextField as setInitialText for Facebook/Twitter posts.
What we want to do is: add an additional permanent message or URL to the Facebook/Twitter posts.
How can we do this? Here's our current code:
[slComposeThirdViewController setInitialText:[[self QText]text]];
[self presentViewController:slComposeThirdViewController animated:YES completion:nil];
It's a little involved, so bear with me here... and this only works for SLServiceTypeTwitter
For anyone reading this that is interested in using this, I've put a sample project on Github: https://github.com/NSPostWhenIdle/Immutable-SLComposeViewController
The first thing you'll want to do is make sure that your view controller conforms to UITextViewDelegate. You'll also want to create an iVar for a UITextView. You won't actually be creating a text view, but you'll want to have a pointer to assign directly to the text view inside the SLComposeViewController. While you're here make a iVar for the permanent string as well.
#interface ViewController : UIViewController <UITextViewDelegate> //very important!
{
UITextView *sharingTextView;
NSString *permanentText;
}
Then in viewDidLoad you can set up what you want the permanent text to be:
- (void)viewDidLoad
{
[super viewDidLoad];
permanentText = #"http://www.stackoverflow.com/";
}
The code below is a pretty basic IBAction to present the composer with a couple of slight tweaks. First, you'll notice that setInitialText uses a formatted string the append the permanent text to the end of the contents of the text field with a space added in between.
Then comes the important part! I've added a loop to presentViewController:'s completion handler to cycle through some subviews of subviews of subviews in order to identify the UITextView in the composer that contains the sharing text. This needs to be done so you can set that text view's delegate in order to access the UITextViewDelegate method shouldChangeTextInRange.
- (IBAction)exampleUsingFacebook:(UIButton *)sender {
if([SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])
{
SLComposeViewController *sharingComposer = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
SLComposeViewControllerCompletionHandler __block completionHandler=^(SLComposeViewControllerResult result){
[sharingComposer dismissViewControllerAnimated:YES completion:nil];
};
[sharingComposer setCompletionHandler:completionHandler];
[sharingComposer setInitialText:[NSString stringWithFormat:#"%# %#",[[self QText]text],permanentText]];
[self presentViewController:sharingComposer animated:YES completion:^{
for (UIView *viewLayer1 in sharingComposer.view.subviews) {
for (UIView *viewLayer2 in viewLayer1.subviews) {
if ([viewLayer2 isKindOfClass:[UIView class]]) {
for (UIView *viewLayer3 in viewLayer2.subviews) {
if ([viewLayer3 isKindOfClass:[UITextView class]]) {
[(UITextView *)viewLayer3 setDelegate:self];
sharingTextView = (UITextView *)viewLayer3;
}
}
}
}
}
}];
}
}
Important: Please note that the above will only work if placed in the completion handler.
Below is an example of how to set up shouldChangeTextInRange to compare the range that the user is attempting to edit to the range that contains your permanent text. By doing so, the user will be able to make changes to any part of the text that they want... except for the part that contains your permanent text. You'll also notice that inside this method I've compared textView to shareingTextView, the pointer we assigned to the text view inside the composer. Doing so will allow you to use other text views within this controller without them following the same rules I've configured for the text view inside the composer.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if (textView == sharingTextView) {
NSRange substringRange = [textView.text rangeOfString:permanentText];
if (range.location >= substringRange.location && range.location <= substringRange.location + substringRange.length) {
return NO;
}
}
return YES;
}
Hope this helps!
You can roll your own composer, append your text and then use SLRequest to actually submit it to the service.
How about capturing the user entered text from the UITextField and then constructing a final string which appends the permanent message you want from it?
UITextField *textField;
NSString *enteredText = [textField text];
NSString *finalPost = [NSString stringWithFormat:#"%# Your permanent text/URL", enteredText];
Now post the "finalPost" string to Facebook or Twitter.
If you want to append the text with URL, just use the addURL: method. It won't display any text in the SLComposeViewController's view. But will add the URL at the end after publishing users tweet.
SLComposeViewController *slComposeThirdViewController = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
[slComposeThirdViewController setInitialText:#"initial Text"];
[slComposeThirdViewController addURL:[NSURL URLWithString:#"http://stackoverflow.com/"]];
I'm beginning to learn how to localize iOS applications and hit a wall while trying to localize my UITabBarItems.
Note that these were created in interface builder (using XCode 4).
Is there a way to do this or would I need to create the UITabBarController using just code and manually inserting a localized string for each UITabBarItem?
Cheers
PS:
I do know that I can set the tile of a UITabBarItem by setting the view controller's title like so:
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = NSLocalizedString(#"Test", #"");
}
... but this only woks once you hit the tab bar item. Before that it just shows what you put in interface builder...
It seems to work if you set title in awakeFromNib instead:
- (void)awakeFromNib
{
self.title = NSLocalizedString(#"Test", #"");
}
On Swift 3 and 4:
override func awakeFromNib() {
super.awakeFromNib()
self.title = NSLocalizedString("Test", comment: "")
}