imageNamed and localization issue - ios

I've localized my images via Xcode, and all the images are in the proper lproj folder.
However, when I use imageNamed I don't get the correct image, but I keep getting the default image even if I change language.
_myImageView.image = [UIImage imageNamed:#"image_name"];

This kind of issue made me waste some time, but then I realized it was a false issue and I hope to save you precious time.
If you switch language and the images are not in the new language, that's because of cached images. The documentation is straightforward:
This method looks in the system caches for an image object with the specified name and returns that object if it exists
Hence, when you switch language, and you ask for the same name to the cache, you'll get the old image! That's it. :)

Instead of:
_myImageView.image = [UIImage imageNamed:#"image_name"];
use:
NSString *imageName = NSLocalizedString(#"image_name", #"image_name");
_myImageView.image = [UIImage imageNamed:imageName];
and localize your image names using the Localizable.strings files like any other resource in your project.

Related

How to localize the images in Images.xcassets?

We can localize an image in the File Inspector using Localize... like this:
Then we can get this:
But now, I use Images.xcassets to manage my images in my iOS project, how should I localize these images in Images.xcassets?
If you are using an Asset Catalog:
Asset catalog elements are now localizable. In the information panel for an asset, there is a "Localization" section that lists all the languages you've set up for your project in the project settings. If you don't select any of them, your images will be deemed "universal" (i.e., they will adopt the default behavior). If you select one or more of them, you will be able to select different images for "universal" and any alternate language.
For example, if your base language is English, and you want Spanish-language alternates, select only "Spanish" and drag in the alternate versions in the Spanish wells, and leave the English versions as the Universal. Then, at run-time, if the chosen language is Spanish, then the Spanish-language image will be pulled. Otherwise, it will pull the English version. (Or, if you want specific English and Spanish versions that are both different from "everything else", also check English and drag in the corresponding English and Universal images.)
If you need to determine localized images at run time without using the Asset Catalog:
While there's (apparently) not currently a way to explicitly localize the xcassets file directly, you could instead localize the name of the asset to pull using your Localizable.strings file. For instance, say you have a graphic logo treatment for the main menu of a game that needs to be localized. You could put the localized logo treatments in the assets file with different names, add the names to your Localizable.strings file, and then fetch the appropriate image with code like this:
UIImage *img = [UIImage imageNamed:NSLocalizedString(#"MAIN_MENU_IMAGE", nil)];
That "Localize..." button is back in Xcode 11! So now you can localize your images and assets in the Asset catalog as you expect:
When Apple gives you lemons, make lemonade, or in this case, lemonade_en, lemonade_es or whatever suits your needs.
First, I create an entry for each desired language in my assets file like so:
Next, you will need to get a language code for the device. The important thing to remember here is that the user may have an unsupported language, so if that is the case, return your default (I use English).
The following extension of UIApplication will handle all of this for you:
extension UIApplication {
var languageCode: String {
let supportedLanguageCodes = ["en", "es", "fr"]
let languageCode = Locale.current.languageCode ?? "en"
return supportedLanguageCodes.contains(languageCode) ? languageCode : "en"
}
}
This extension does the following:
Creates an array of the languages my app supports
Obtains the current device's language code, or returns a default value if this is nil
Finally, it checks to see if the current code is in my supported list. If it is, it returns it, otherwise it returns my default code
Now, we combine the two to get the proper image:
let languageCode = UIApplication.shared.languageCode
let image = UIImage(named: "access_\(languageCode)")
After some search on the Internet, I think this feature is not provide by xcassets right now. Therefore, just don't use xcassets to manage your localization images.
I found another way:
add in you asset.xcassets a folder for each language (for instance: en,it,de)
set the flag "Provides Namespace" to each folder
copy all images and assets in that folder
In your code load the assets by calling
let languageCode = UIApplication.shared.languageCode
let image = UIImage(named: "\(languageCode)\assetname")
At the moment, I pull out the images that needs localization from Images.xcassets and put it in the standard project folder. I leave the non-localized images in the .xcassets. I use your method for the images that need to be localized.

Path Parameter in UIImage imageWithContentsOfFile: for XCAsset

Is there any way to retrieve a UIImage object with imageWithContentsOfFile: if the image is stored in an xcasset Bundle (Xcode 5 Image Catalog)?
It's easy to access it with the imageNamed: method, but imageWithContentsOfFile: has a slightly different behavior, so what do I pass in the path parameter?
I've tried to use a variety of formats, but they all return a nil value:
ImageName#2x.png
ImageName#2x
ImageName
NSBundle File Path to Image
Apple's documentation doesn't specify a format for this. Any ideas?
You can't load an image from xcasset via imageWithContentsOfFile:, because every entry in xcasset is compiled into a single binary file Assets.car. The format of this file is not documented and yet not reverse-engeneered well afaik (see "What you should know if you want to try reverse-engineering this stuff." section for explanations).
This is done for performance and low memory consume reasons, that's why imageNamed: is a good solution.

UIImage imageWithContentsOfFile: - include file extensions or no?

Are there any negative implications of excluding the file extension when using [UIImage imageWithContentsOfFile:] or is it best to include the file type in the path?
The Apple Docs state that initWithContentsOfFile: requires the following path as its parameter:
The path to the file. This path should include the filename extension that identifies the type of the image data.
However, the path for [UIImage imageWithContentsOfFile:] just states:
The full or partial path to the file.
I was just wondering if it's still best practice to include the file extension or to not even worry about it. Thanks!
You should include the file extension for all file requests. The only instance I can think of where you would not is using the
NSBundle pathForResource:ofType:
method. That method you specify the file extension outside of the file name. I'm relatively certain the "partial path" refers to relative paths. You can ask for a file in the root of your bundle by just the file name as it's path relative to your bundle is in the root, in other words just the file name.
The best way is to use NSBundle pathForResource:ofType:
If you have those images:
"myImage.png" (iPhone)
"myImage#2x.png" (iPhone Retina)
And you want to load it to an UIImage, you will use this code:
UIImage *myImage;
myImage = [UIImage imageWithContentsOfFile:[NSBundle pathForResource:#"myImage" ofType:#"png"]];
This code will also load the "myImage#2x.png" in iPhone Retina with no need to add the #2x to the code.
Be sure to write the exact file name. "myimage" might not work on device since the original file name has a Uppercase "I" ("myImage.png").

What overhead is there #defining objects?

We have a constants file and want to be able to whitelabel our app.
Part of the white labelling is defining images that can be swapped by our clients.
What overhead would defining all these images be?
e.g. #define kMPNavigationBarBackgroundImage [UIImage imageNamed:#"nav_bar"]
Would it be worth defining NSString constants for the image names? Or will this be ok?
Bear in mind there will be hundreds of these images, are they all going to load into memory at this point? Or is the #define just a palceholder for lines of code which won't get run until they are called?
Thanks
"#define" is preprocessed by the compiler, before the compilation takes on all your kMPNavigationBarBackgroundImages will be replaced by your definition. It doesn't have anything to do with runtime.
http://www.cplusplus.com/doc/tutorial/preprocessor/
When you use #define to define some sort of constant it is just a preprocessor directive to tell it to replace the defined text in the code.
So if you use:
#define image [UIImage imageNamed:#"name"];
UIImage *myImage = image;
Then before compilation it will get changed to:
UIImage *myImage = [UIImage imageNamed:#"name"];
It just gets replaced everywhere that you use it.
Hope that helps!
:)
Well in short, your last statement is correct; the code which forms part of the #define won't be evaluated until it's referenced in the code.
Perhaps a better approach to the issue would be to put all these Assets into a dictionary that can be optionally swapped out by "the client" if they wish. The dictionary would map the well known name to the asset filename.
The issue with using #defines is that it relies on the customer putting the right code in the definition, which is tedious and error prone, for example:
// (Missing end quote)
#define kMPNavigationBarBackgroundImage [UIImage imageNamed:#"nav_bar]
Will cause a non-obvious compilation warning.
A more elegant approach would be to provide a method (somewhere) where you simply supply the well known name:
- (UIImage *)imageWithWellKnownName:(NSString *)wellKnownName;
Which looks-up the asset filename and loads it, throwing an exception if the file could not be loaded.

Loading an image programmatically in iOS

In my App for the iPad I'm trying to load an image file programmatically. The file is NOT part of my project, hence it is not referenced in XCode. There is no entry for the file in XCode's Groups and Files column.
The image has to be loaded at runtime instead, it name being read from a Property List.
I'm trying to load the file like this:
NSString* pathToImageFile = [[NSBundle mainBundle] pathForResource:#"MyImage" ofType:#"png" inDirectory:#"MyDirectory"];
UIImage* retVal = [UIImage imageWithContentsOfFile:pathToImageFile];
In this case, mydirectory lives in the main bundle like this:
MyAmazingApp.app/MyDirectory/MyImage.png
Unfortunately, the Image will not load. I can't add the image file to my project, as its name is to be determined at runtime and cannot be known in advance. The file name is read from a config file (a Property List) at runtime instead.
What am I doing wrong? Your help will be very much appreciated.
You can use NSFileManager to get the contents of a directory, or several directories. You obviously have to know something about the file you want to load so that you can identify it, but you could for example use NSFileManager to help you generate a list of images in your app bundle and in your app's Documents directory.

Resources