Swift update text on language change - ios

I have been searching for hours and I have not been able to find an answer. I'm making an app which uses multiple languages which the user can change during runtime, this is necessary for the product. Now I have been able to change the language with the following code:
userDefaults.setObject(["\(cc)"], forKey: "AppleLanguages")
userDefaults.synchronize()
This code works great. If I change the language setting from Dutch to English it does actually change. The underlying code that is. My app makes use of a server and when I try to logon to the english or dutch service with different credentials it recognises them and lets me login. However the view does not change until after a restart. I tried making it change with this:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
check.text = NSLocalizedString("currentLangauge", comment: "Just a check")
}
And the check label has been declared as:
#IBOutlet weak var check: UILabel!
After a restart it updates just fine but not during runtime and I know it should be possible because I have seen apps do it. I found some examples in Objective-C that use NSBundle but I am not familiar with Objective-C and I do not know how to convert this to Swift.
One of the examples in Objective-C: iOS Change app Language doesn't take effect
Does anybody has any experience with this?

You need to manually manage the process unfortunately. You'll need to write your own replacement of NSLocalizedString class, that checks user defaults and reads the language, then loads the string from the correct strings file.
You'll also need a mechanism to notify labels / text views / buttons that they should re-render their titles when the user changes language, as when the setting is changed, it's likely there will be a fair few views displaying text in memory. A crude solution is to subclass the foundation classes, initialise them with a string key, with which they perform their own text lookup. Then have them respond to a language changed notification that you fire when the user changes language. They can then perform another string lookup with your NSLocalizedString replacement class.
It's actually not as much effort as it sounds, but it is an effort. It's probably worth understanding string localisation as much as possible. Here are a couple of good resources.
http://nshipster.com/nslocalizedstring/
http://www.objc.io/issue-9/index.html

Eventhough "P-double" gave a very good suggestion on how to work around this. I couldn't really figure out a way to really implement this so I went with a different solution that may just take a tad bit more. Instead of just using the "Localizable.strings" file, I decided to just make a new strings file for each langauge and call them by the language identification (eg. mexico = es-MX, english = en).
Instead of rewriting NSLocalizedString, I opted to add the "tableName" option which can then default to the right .strings file. So when I change language now I do it like so:
cc = currentCell.langString
userDefaults.setObject(api, forKey: "api")
userDefaults.setObject(["\(cc)"], forKey: "AppleLanguages")
defaultTable = cc
userDefaults.synchronize()
And when I ask for a localized string I do it simply like so:
check.text = NSLocalizedString("currentLangauge", tableName: defaultTable, comment: "Just a check")
I can use this for everything that I can show on my storyboard, the only thing I have to do extra is really define every single object that is on the storyboard so I can adjust the text. With this "solution" you do not need to localize your storyboard anymore. Only the code.

Related

A better way to do Language in swift 3

i am currently doing an app that has language change feature. However,for every string that i wish to have changes made to when a different language is selected, i have to implement the following code
"Hello world".localize()
However, as the app gets bigger, the code become very messy in the way that all the strings in their respective view controllers have this .localize() append to it.
Is there a way where i can do this .localize() thing in one central place?
EDIT: I tried to create a Strings.swift file and put all the strings inside. I did something like this.
static let relevantString = "hello world".localize()
and then in the view controllers i call
let myString = relevantString
However, this does not well. The strings will only change after i terminate the app and restart it.
Your attempt to use static let fails to produce the dynamic behaviour you want simply because you used a constant. You could use a read only computed property instead, something like:
var relevantString : String { return "Hello World".localize() }
As an alternative, as you seem to be more concerned over the clutter, you could define a simple prefix or postfix unary operator which called localize() on its argument. Swift allows a number of symbols to be used as operators so the operator can just be a single character. For example you could define, say, § so that §"Hello World" produced a localised string.
HTH

Practical application of backticks in Swift

From this document:
To use a reserved word as an identifier, put a backtick (`) before and after it.
I'm curious about the practical application of this. When would you actually want to name something `class`, `self`, etc.?
Or, relatedly, why did the designers of Swift allow this, rather than just forbidding us from using reserved words as identifiers?
The most important usage is the interaction with other languages that have different keywords.
From Swift you can call C and Obj-C functions.
Now, consider for example that you need to call a C function called guard. However, that's a keyword in Swift, therefore you have to tell the compiler that you don't want to use it as a keyword but as an identifier, e.g.:
`guard`()
There are multiple keywords in Swift that are widely used as method/function names, e.g. get and set. For many contexts Swift is able to figure out the difference but not always.
In some cases using guard give us nice example for this purpose.In such scenario I need check self variable life time if not exist anymore (current controller deallocated) I don't want to execute rest of code.
guard let `self` = self else {
return
}

Create Header and implementation file in swift

This is more of a coding style question but i believe it is valid. Coming from an obj c background i always create a .h and a .m when creating a class. However with swift that changes and all that goes into a single file. I know that for some people this is cool but i miss having these two things separate.
Quoting a comment from Zaph
"What I miss is a list of public methods as opposed to searching an
entire source file for methods not marked private. There is a
programming concept of "writing to the interface". And the public
methods should be carefully picked, not just because the developer
forgot to make some private."
is there a way to have a header - implementation class in separate files using swift? Maybe some trick?
Thanks
May be you can use Generated Interface to view all the public methods and properties. You can find that option at the bottom of related files popup in the upper-left of the source editor window. To switch back, click Original Source in the same pop up.
Shortcut: control + cmd + down arrow
This is how generated interface file looks.
As far as i'm aware, this cannot be done. That being said, if set out your .swift files correctly then they are still very readable. I tend to follow this as a guideline for styling in swift and i find that it breaks things up into readable sections, especially by using // MARK: as well.
In short, no.. But what do you miss..? Once you get used to it, you will probably prefer it like this! The old separation has no clear advantage over this new one!
More and more languages use this approach, as it reduce coupling and errors.
So when you change the signature of a function, to need to check another file to update it, it's only duplication without any added value.
The problem you describe (how to see only "public" functions) is usually done buy tools( IDE) or documentation generators.
You can create 2 swift files:
YourClassNameHeader.swift
class YourClassName {// put here all your properties
}
YourClassNameMethods.swift or YourClassNamePrivate.swift
extension YourClassName { // put here all private methods and
properties }
But in general its not good practise

How should I store localized Strings in an iOS project?

I’m developing an iOS application. One of my tasks is localization. For this reason, I’ve taken all strings and put them each in a NSLocalizedString(key,comment), and everything’s been OK.
But my new solution is to create a Singleton class, which stores every String that I use in the project. With this I have one small, but tricky problem: naming. Do I need to create some dictionaries for every class (view) that needs localized strings? Or should I use prefixes for this, or functions that return objects with good, understandable property names?
P.S: i don't want to invent new wheel. I want to create STORAGE of localized strings, that will be used in project. So, my target is to make singleton:
[[[StringStorage sharedInstance] getStringsForClass:self] objectForKey:#"title"];
or something like this:
[StringStorage sharedInstance].stringTitleForMainView
You really should stick with NSLocalizedString and NSLocalizedStringFromTable. You also can use the NSBundle method localizedStringForKey:value:table:.
But if you take any other route you are reinventing the wheel, and you lose the ability to use the genstrings command line tool to extract all your strings from the source code.

iOS - Add "objects" to existing app (jailbroken)

How do you add "objects" to an existing app ?
For example, the EasyRefresh for Chrome tweak, enables a new button inside the iOS Chrome app, as do many other tweaks.
How may i add a simple UIButton to, for example, the Twitter app ?
Is there any GitHub projects that might help me to understand how it's done ?
Image Source: ModMyI
Thanks.
The trick involves some (very basic) reverse engineering and is made up of several steps; I'll try to explain them as clearly as possible.
Step Zero: if the app is downloaded from the AppStore, it's encrypted. You have to decrypt it using one of the scripts/applications normally used to crack apps; one command line script is poedCrack.sh (google it, you'll find it quickly on one of the paste sites), one GUI application is Crakculous (it's available in Cydia). Note that one of these are needed for easy (automatic) decryption -- the manual decryption method is way too involved to put in a StackOverflow answer, that's why I'm suggesting these tools.) However, I don't in any way encourage you to crack apps! (Basically I'm asking you not to use these tools for their original purpose :) If you want to have a look at the manual decryption process, head here.
Step One: you need to do what classes the application uses/creates. For this, you need the class-dump or class-dump-z utility. This command-line application reverses the app's binary executable file and generates interface declarations for all Objective-C classes the app uses and has inside. You can find class-dump-z, the more advanced and preferred variant here.
Step Two: after you have the class declarations, you'll have to guess which class does what and when (yep, a bit confusing). For example, in one of the files generated from above app, Google Chrome, by class-dump-z, you may find something similar:
#interface ChromeUrlToolbar: UIToolbar {
UISearchBar *urlBar;
}
- (id)initWithFrame:(CGRect)frame;
- (void)loadURL:(NSURL *)url;
#end
Well, that sounds good, doesn't it? You can see that its implementation has an initWithFrame: method (as all UIView subclasses) -- why not try to modify it?
Step Three: for this modification, you'll need MobileSubstrate. MobileSubstrate is a developer library created by Saurik, the creator of Cydia, in order to make code injection to apps easy. You can find some really good tutorials on the web, including this one.
So, you've got a class and you wanna 'hook' it -- so you write some code like this:
static IMP __original_init; // A
id __modified_init(id __self, SEL __cmd, CGRect frame) // B
{
__self = __original_init(__self, __cmd, frame); // C
// D
UIButton *newButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[newButton setTitle:#"Chrome Pwned"];
newButton.frame = CGRectMake(0, 0, 100, 40);
[__self addSubview:newButton];
return __self;
}
// E
__attribute__((constructor))
void init()
{
Class clazz = objc_getClass("ChromeUrlToolbar"); // F
MSHookMessageEx(clazz, #selector(initWithFrame:), __modified_init, &__original_init); // G
}
Explanation: let's begin from the end. The init function (E) is declared __attribute__((constructor)). That means it's automatically called when the library we'll create out of this code will be loaded into Chrome. That's exactly what we want beause we want to alter our application's behavior prior to having started it.
On the line marked // F, we capture the class object itself we want to modify. Objective-C is a highly dynamic language; that means we can get and modify information about the classes and objects at runtime. On the line marked // G, we use the most important function of the MobileSubstrate API: MSHookMessageEx. To understand how it works (rather what it does), you must know the following: Objective-C itself is implemented as a plain C library -- the language itself, under the hoods, is just simple C. So every message send in Obejctive-C is actually a C function call. These C function have two special arguments: self and cmd -- the former is the pointer to the object being messaged, the latter is the selector (a special, unique pointer to the name of the message being sent). So what MSHookMessageEx does is it takes a class and a selector, finds the implementation of the function corresponding them, and exchanges that function with the function supplied in its 3rd argument itself (__modified_init in this case). In order not to lose data, it also returns the function in its 4th parameter (here it's __original_init).
So, now the initialization of the Chrome URL toolbar is redirected to our function, what to do next? Well, nothing special: first we just call the original initialization function (notice the first two special arguments, __self and __cmd!) which creates the toolbar as if normally (this line of code is denoted by // C). Then, we do the actual alteration: in section // D, we create an UIButton, set its title and place, and add as a subview to our freshly created toolbar. Then, knowing this is an initalization function, we return back the original instance along with our button's code injected into it.
Well, that's basically what you'll need to know about this; if you're interested in deeper details of how Objective-C works and how you can create cool iOS tweaks, I suggest you to read Apple's official documentation on the topic and you can browse through some of my opensource Cydia tweaks. as well.
I hope this will help you!
You need in order to do this to understand how the Objective-C runtime works. Especially the messaging system (ie. calling a method). In particular, the methods to call are determined at runtime, vs other languages where it is at compile time.
This allows for global changing of particular methods, aka method swizzling.
Using the Mobile Substrate library you will be allowed to replace any method implementation with your own, and even call the original implementation. You need for that, of course, to know the method's name and the argument it takes, as well as the class it belongs to.
So to modify the SpringBoard for instance, you'd have to know which class in contains and which method. You'll have to use the class-dump or class-dump-z utility which does that for you (class-dump-z is more recent and more used for iOS dev, class-dump is more versatile and compatible with older binaries as well as 64 bit).
So to class-dump the SpringBoard, you'd need to enter in Terminal.app
class-dump -H /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk/System/Library/CoreServices/SpringBoard.app/SpringBoard -o ~/Desktop/SpringBoard
For class-dump-z, the -p option will generate #property instead of getters/setters, which is more clear, so you'd probably type in
class-dump-z -p -H /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk/System/Library/CoreServices/SpringBoard.app/SpringBoard -o ~/Desktop/SpringBoard
That line will create a folder on your desktop with all the classes definitions of SpringBoard.
Of course you might need to change path to one suited to your system (about that, for recent versions of Xcode, the Developer folder is in Xcode, so you'd need something like
/Applications/Xcode/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.1.sdk/System/Library/CoreServices/SpringBoard.app/SpringBoard
You can also find on the internet people who did that for you for most of the existing frameworks, this is pretty handy if you make sure they are at the right version for you're system.
Now, for AppStore applications, you will first need to decrypt them as they are protected. You will probably need to find the names and links of that yourselves as this is probably against the ToS of Stack Overflow, though using gdb can achieve that purpose.
To ease the work, some tools such as Logos (you will probably also need to see Theos) has been created that reduce the boilerplate code needed. There also is a (quite old) xcode template & tutorial for mobilesubstrate that provides good help.
Logos makes it easy to hook method method from class classname :
%hook classname //declares the class from your application you're going to override
-(void)method {
dosomethingnew(); //put all your new code for the method here
return %orig; //this calls the original definition of the method
}
%end //end hooking classname
For a list of the frameworks in the system and what they are usefull to, see here
Last thing : a list of popular tweaks that are opensourced (links to GitHub when possible) :
Chrome Customizer
MobileNotifier
UISettings (iOS 5 version was here also but seems to be pulled ?)
Spire
IconRenamer
Maxoimizer
IconRotator
QuickReply
WinterBoard
Veency
Some little tweaks
Finally, have a look at the WeekTweak, they release opensource tweak each week so you can learn by looking at someone else's source to try & do your own stuff. And the #theos chan on IRC (irc.saurik.com) will also provide help if you ask it kindly.

Resources