Multilingual iOS App - iphone

Now, in standard behavior of localization, the iOS determines the currently set Language of iPhone and uses the Localizable.strings file to set the appropriate text.
My client, however, requires a multi-language iOS application in which the language is set within the application independent of the native iOS preferred language. i.e. the application may have different language to what the iOS is currently set to on the iPhone.
Anybody with any ideas about how to go about implementing this scenario?
My idea:
I could create a custom static class similar to the NSLocalizeString and hard code strings within that and return appropriate language string w.r.t language set within the app, and if that is a possible solution then any suggestions about how to structure that class)

You could:
store your translation string in a .plist file (string_key/translation) for each language;
read the appropriate plist (depending on the language currently set) in a NSDictionary;
access the dictionary for each string you want to display (just like you would do with NSLocalizeString).

I once had to create a flashchards app and the client needed to change the language at will. I don't have the source code for it at my box right now, but I remember using this tutorial. Also check the sample code they use.
Side note - dissing your clients publicly is really unprofessional.

Create plist files with needed language. I use {LANG}_{CLASS NAME} and {CLASS NAME} for default with all localized strings.
After that I made a method that checks if proper file exists, take it or default if missing and returns an NSDictionary object depending on the device language, called on class init. This idea can also be used, if you implement localized nibs by adding nib name to the strings file.
+ (NSDictionary *) getLocalized: (NSString *) contollerName andLang:(NSString *) lang {
NSString *fullPath = nil;
// You can use device lang if needed
NSString * curLang=[[NSLocale preferredLanguages] objectAtIndex:0];
fullPath = [[NSBundle mainBundle] pathForResource: [NSString stringWithFormat:#"%#_%#",contollerName,lang] ofType: #"plist"];
if (!fullPath) {
fullPath = [[NSBundle mainBundle] pathForResource:contollerName ofType:#"plist"];
}
return [NSDictionary dictionaryWithContentsOfFile:fullPath];
}
So, it can be called by something like that:
[tools getLocalized:[Controller.class description] andLang:#"XX"];

You can use standard localization (.strings files and localized .xibs) and force your app to use a language other than the iOS language setting. For details on how to achieve this, see this post: Change language of the ios application
Note that if you (or rather your client!) want to switch language on the fly while using the app, it will be more complicated--you would need to implement some sort of refresh feature and make sure the xibs are reloaded.

Related

How can i change display name with code

I want to dynamically change display name from within the application. Is there a way it? i dont want to use .plist. i want to use with xcode from application. Thanks
${PRODUCT_NAME} How can i call this value with code ?
The app name is defined in the app's info.plist file..
That come in Main Bundle..and iOS does not allow anything to be written and updated in Main Bundle..
So you cant programmatically change contents of app info.plist having app's name...bundle identifier ..etc
No, it's not possible. The Info.plist and everything in the app bundle is read-only once it's installed on an iPhone.
Add the following code to get the product name
NSDictionary *infoPList = [[NSBundle mainBundle] infoDictionary];
NSString *appName = [infoPList objectForKey:#"CFBundleDisplayName"];
appName is the value of "Bundle display name" or Product Name
I don't believe so.
You can localise it -- i.e., display different names depending on the language in use -- but I don't think you can change the name dynamically. That's probably a good thing... it could get pretty confusing if abused.

Display encrypted file using QuickLook framework or UiDocumentInteractionController

I have an encrypted word/excel/pdf file locally stored which I need to preview in my iPad app. I understand that QLPreviewController or UiDocumentInteractionController could be used to preview these files. I can very well use this
- (id <QLPreviewItem>) previewController: (QLPreviewController *) controller previewItemAtIndex: (NSInteger) index {
return [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:[documents objectAtIndex:index] ofType:nil]];
}
But the file is encrypted and when I decrypt it I would get hold of NSData object. How do I go about loading NSData in either of these.
Also I understand that I can very well store the NSData back as a local file and load it in Preview. But there is a constraint of not storing the unencrypted file locally.
If someone has already accomplished this and can help me out here it will be greatly appreciated.
Thanks
AJ
Since you are using Quick Look, your options are limited. You must give Quick Look an NSURL, which means it must be on the file system (or the Internet). Fortunately, this shouldn't be much of a problem. iOS devices use hardware-level encryption. When your file is encrypted, only your app has the key to decrypt it. So, your file will still be encrypted, but it will also be readable by your app and only your app.
Here's what you do:
Decrypt your file into an NSData object, which you've already done.
Write the file to a location that will not get uploaded to iCloud nor backed up by iTunes. The tmp directory is probably the best choice. The code looks something like this:
NSData * data = // Your decrypted file data.
NSString * fileName = // Whatever you want to name your file.
NSString * path = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
NSURL * url = [NSURL URLWithString:path];
NSError * error = nil;
BOOL success = [data writeToURL:url
options:NSDataWritingFileProtectionComplete
error:&error];
if (success) {
// Give the URL to Quick Look.
}
else {
// An error happened. See the 'error' object for the details.
}
At this point you have an NSURL which you can use with Quick Look. Don't forget to delete the decrypted file when you are done with it.
There are a few things to note about on-disk encryption:
It is only supported on iOS 4.0+.
It may not work on "older" devices.
The user must have an active passcode.
If you use NSDataWritingFileProtectionComplete, the file is not accessible while the device is locked. If you need to access the file while the app is locked, then you should use NSDataWritingFileProtectionCompleteUnlessOpen or NSFileProtectionCompleteUntilFirstUserAuthentication instead. This will still give you great protection, even if the device is stolen and jailbroken. Be aware, though, that these encryption options are only available on iOS 5.0+
For more details for on-disk encryption, check out the iOS App Programming Guide
After doing some digging, I found out that QLPreviewController is using UIWebView underneath, and calls the loadRequest: to load the requested file.
Another way to accomplish what you desire is to make a private Category on UIWebView,
and use method swizzling to override the loadRequest: method, and call instead the loadData:MIMEType:textEncodingName:baseURL: method.
Beware that:
1) In low-memory scenarios (i.e. large files) a black screen with
"Error to load the document" appears, if that concerns you. (The
unhacked QLPreviewController knows how to handle these scenarios
very well and present the document).
2) I'm not sure Apple are going
to approve this kind of hack, although no private APIs are used
here.
code:
#implementation UIWebView (QLHack)
- (void)MyloadRequest:(NSURLRequest *)request
{
// Check somehow that it's the call of your QLPreviewController
// If not, just call the original method.
if (!insideQLPreviewController)
{
// Call original implementation
[self MyloadRequest:request];
}
else
{
// Load the real data you want
[self loadData:data MIMEType:mimeType textEncodingName:nil baseURL:someURL];
}
}
+ (void)load
{
method_exchangeImplementations(class_getInstanceMethod(self, #selector(loadRequest:)), class_getInstanceMethod(self, #selector(MyloadRequest:)));
}
#end
Actually, writing a file to a tmp directory is still insecure. The other alternative is to use UIWebView with NSURLProtocol and allow decrypting this data on the fly.
One way could be.
use Temp Dir , Save File in Temp , Make NSURL From that Temp File and Display and then Delete that temp Dir after that.
Thanks.

How to switch skins (or design themes) in iOS app?

I'd like to make my iPhone app to be able to switch between skins (or design theme, or look and feel, such as wooden, metal, earth color, men's, girls, etc...).
I'll prepare some sets of skins that contains images for buttons and backgrounds, sounds, and text color, and let the user decide which set of skin they want to use by the application settings.
What is the best practice to implement this?
The conditions are:
I'd like to use Interface Builder
I need to support iOS 3.1.3 and later
I want to make the sets of skins downloadable from the internet (I can't bundle all the skins in the app, as one set of skin requires lots of images and the app file size could become huge if I do so... I also don't want to hardcode any information about specific skins.)
If a custom skin does not contain one or some elements, (such as an image or sound file), I want it to use the missing element from the default set of skin.
I don't want to create Nib files for each skin. The Nib file for one screen should be the only one in the main bundle for easier maintenance.
I'm thinking about making a superclass of all the UIViewControllers in my app and override the part that it loads Nib file, and instead of loading from the main bundle, load the resources from the skin that is saved in the Document directory... but I don't know how to do it... The default behavior of the Nib-loading methods always loads resources from the main bundle and the information about resource file names are lost after reading... :(
Thanks in advance for your help.
Am not sure about best practice .. But, if your app is not big enough, then a well structured plist is your friend.
Initially, you could choose: Metal Theme. The following should hold:
You either have a Singleton ThemeManager, or just stick an NSDictionary to one of your Singletons if appropriate.
The point behind the ThemeManager is the mapping between the asset and the theme..
Some sample code (written directly on SOF .. Don't mind Syntax mistakes):
#define kThemeMap(__x__) [[ThemeManager sharedManager] assetForCurrentTheme:__x__]
...
-(void)doUselessStuff {
UIImage* backgroundImage = [UIImage imageNamed:kThemeMap(#"FirstViewBG")];
...
}
//in the ThemeManager:
//returns the appropriate name of the asset based on current theme
-(NSString*)assetForCurrentTheme:(NSString*)asset {
//_currentTheme is an NSDictionary initialized from a plist. Plist can be downloaded, too.
NSString* newAsset = [_currentTheme objectForKey:asset];
if(newAsset == nil) {
newAsset = [_defaultTheme objectForKey:asset];
}
return asset;
}
//Let us assume the user selects Metal Theme somewhere .. Still coding ThemeManager:
-(void)selectedNewTheme:(NSString*)newTheme {
//First, get the full path of the resource .. Either The main bundle, or documents directory or elsewhere..
NSString* fullPath = ...;
self.currentTheme = [NSDictionary dictionaryWithContentsOfFile:fullPath];
}
The plist files are just a dictionary with string to string mapping... something like this:
//Default.plist
#"FirstViewBG" : #"FirstViewBG_Default.png"
#"SecondViewBG" : #"SecondViewBG_Default.png"
#"WinSound" : #"WinSound_Default.aiff"
//Metal.plist
#"FirstViewBG" : #"FirstViewBG_Metal.png"
#"SecondViewBG" : #"SecondViewBG_Metal.png"
#"WinSound" : #"WinSound_Metal.aiff"
Alternatively, you can just save the postfix, if that is good enough for you.. But, it will require string manipulation, by slicing the extension -> adding the postfix -> adding the extension ..
Or maybe make it a prefix?
You can category on UIImage with the methored imageNamed:, use the custom imageNamed: instead of the default one.
In the custom methord, selected the image by theme.

Link to pdf within app

I've got an iOS app that at one point opens a link to a website in a webview. These links are kept in a plist file (so it is easy to maintain them as the app evolves). What I want to do next is to also link to PDF's (or any picture of text file format, of even a html format, this is flexible) that are kept within the app. And I would like to do this as much as possible from within the existing app structure. So, is it possible to create a link that can be put in the plist as a web-link, but instead opens a file on the device itself (possibly in the webview)? And how would I go about that? Any ideas?
Thanx in advance for your help!
You will need to create the links at runtime. I would suggest having a certain prefix to a local url, such as mylocalfile:filename. Then, in the code that loads the plist, check for the prefix and create the link when necessary. You could also just create these links once and store them in a separate file, then load that instead of the original.
NSArray *links = nil; //I assumed your plist is an array. Change to dictionary if required
NSString *pathToStoredFile; //Get the path for the file you create with the updated links
if([[NSFileManager defaultManager] fileExistsAtPath:pathToStoredFile]) {
links = [NSArray arrayWithContentsOfFile:pathToStoredFile];
} else {
NSArray *tmp = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"ListOfLinks" ofType:#"plist"]];
if(!tmp) {
//handle error
}
NSMutableArray *tmpLinks = [[NSMutableArray alloc] initWithCapacity:[tmp count]];
for(NSString *link in tmp) {
if([link hasPrefix:#"mylocalfile:"]) {
link = [link substringFromIndex:12]; //12 is the length of mylocalfile:
NSURL *url = [[NSBundle mainBundle] urlForResource:[link stringByDeletingPathExtension] withExtension:[link pathExtension]];
[tmpLinks addObject:[url absoluteString]];
} else [tmpLinks addObject:link];
}
links = [tmpLinks copy];
[tmpLinks release];
[links writeToFile:pathToStoredFile atomically:NO];
}
Yes, I would go with a UIWebView. iOS should be able automatically handle certain URL handlers and your app can register to handle the rest, as necessary.
iOS knows how to handle certain file types already. For example, if Safari (or a UIWebView) encounters http://somesite.com/afile.pdf, it know which apps can handle the file type. Another example is a phone number: skype://8005555555. iOS knows to open Skype and pass the number to it. iOS also knows that iBooks can handle PDf files.
Register your app for the appropriate file handlers and types. Then, users can tap and hold on the link to see a menu of available apps to handle the link. If it's a link that's only used by one app, the user doesn't even need to hold, a tap will suffice.
As far as making a link pointing to a local file, you can, and you would use the C function NSDocumentsDirectory() and append that to a url handler. (Example: http://NSDocumentsDirectory()/filename.pdf)

Localization of views

I am trying to make my app localized. I have followed this procedure http://www.switchonthecode.com/tutorials/a-simple-localization-example-for-the-iphone. And have deleted all localization and done it over again. I've seen other tutorials with exactly the same procedure.
My view to localize is named InfoView.xib.
I have:
rigth-clicked InfoView.xib->Get info -> Make File Localizable
added Localization and named it to "sv" (for Swedish language)
edited the sv-xib
When the simulator is set to English or any other language, I do get the xib loaded and can present it. But when setting the simulator to Swedish language, the view is not instantiated.
When doing
NSLog(#"Language: %# ",[NSLocale preferredLanguages]);
I do get "sv" as the top language code.
("NSLog(#"Locale: %# ",localeString);" gives "sv_SE")
When trying this
[[NSBundle mainBundle] loadNibNamed:#"InfoView" owner:self options:nil];
NSLog(#"InfoView in loadView %#", infoView);
It prints "(null)" when simulator is set to "Swedish" but instanstiate with any other language.
This is my setting if I do Get info on my sv-xib, in case it matters:
I have done the cleant targets and did reset the simulator.
What can the problem be? And how is the mapping done between the name I choose when adding a locale (sv) and the simultar/device language setting? Is it the acutal "sv" I named my locale to that must match the device's language code ("sv" is one)?
And 30s after I posted this a thought struck me; "Why don't I check if the localized view in IB is linked to the outlet in my File's Owner"...
It wasn't... Perhaps the tutorials should mention that the new localized xib must be linked as well. And really, why isn't this done automatically...?
"Thank you Nicsoft!"
"You're welcome!"