Forcing Voiceover to read particular word (homographs) - ios

I want to use the word "live" (l-eye-v) for an event on now rather than "live" (l-i-v) as in "will she live". Is there any way to force this?
The answers to VoiceOver accessibility label for Touch ID are fairly relevant to this question and suggest that the answer is no, there is nothing that can be done to force it. This question title is much more generally applicable and searchable so I think it is a useful addition even if answers do link there. There are also aspects of the linked question that are applicable only to a particular situation.
There is also VoiceOver pronunciation issue: "Live" "ADD" which discusses the specific case of "Live" too worth a read if you find this page now.

A good way to implement this is to override the accessibilityLabel getter property. This way you don't have to track both strings separately, just have a dictionary of words that need phonetic replacement. So for example, if your object were a UILabel, you could do something like this:
-(NSString*)accessibilityLabel {
NSMutableString* mutableResult = [NSMutableString new];
for (NSString* word in [self.text componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#" \t\n"]]) {
if ([word isInDictionary]) {
[mutableResult appendFormat:#" %#", [word phoneticReplacement]];
} else {
[mutableResult appendFormat:#" %#", word];
}
}
return mutableResult;
}

In these cases, what I usually do is just add a separate accessibility string (as opposed to using the text displayed to the user) that contains the word phonetically. So try something like lyve/lieve. Text to speech is a complicated process and requires a lot of AI to work properly with homonyms.

Related

How to always get english string from NSError?

I've got an iOS application that is designed with English-only strings. When running on an iOS device that is using another language for display (i.e. Settings/General/Language/iPhoneLanguage is something non-English) I would like to also show system errors in English since my font can't display non-english characters at the moment.
Is there a way to always get the english error message from NSError? Or to lookup the english error string in the system somehow manually?
After digging around I found a potential workaround.
First, it turns out that in most cases, iOS is returning English strings from [myNSError localizedDescription] when I'm running on a system that has the region set to, say, France, with the language set to, say, French. I believe this is because I have CFBundleDevelopmentRegion set to "en" in my info.plist. This is what I wanted to happen since I am trying to build an app that runs fully in English everywhere (for the moment).
However, in some cases, like if you try to buy something in the app and the network is off (this is my most reliable repro case), I will get NSErrors that return a localized error from [myNSError localizedDescription]. In this case: "Connexion à l’iTunes Store impossible."
When I get an error like this, a workaround that kinda works is to create an NSError manually using the domain and error code from the original. If you then ask it for the localizedDescription it will give you one in English, but it seems to be more general. In this case: "The operation couldn’t be completed.". What appears to be happening is that the system is filling in more detailed errors using the local language.
string GetEnglishLocalizedDescription(NSError *originalError)
{
if(originalError == nil)
{
return "";
}
else
{
NSString *originalNSErrorString = [originalError localizedDescription];
string originalUtf8String = originalNSErrorString == nil ? "" : [originalNSErrorString UTF8String];
// If we have a character outside of the US Ascii range
// Our font won't display it properly and this is probably
// a localized description
// https://tools.ietf.org/html/rfc3629
bool isUSAscii = true;
for(uint8_t c : originalUtf8String)
{
if(c > 0x7F)
{
isUSAscii = false;
break;
}
}
if(!isUSAscii)
{
// Has non-US Ascii characters, probably localized, get the standard error instead
NSString *englishNSErrorString = [[NSError errorWithDomain:originalError.domain code:originalError.code userInfo:nil] localizedDescription];
string englishUtf8String = englishNSErrorString == nil ? "" : [englishNSErrorString UTF8String];
return englishUtf8String;
}
else
{
return originalUtf8String;
}
}
}
Then, the trick is to figure out when you want to do this because you can't really tell in an obvious way that you got an error in the local language. In my case, I can just look for characters that my font can't display and convert it. If my font can display it, I guess I'm OK displaying it since either:
a) it is in english and the whole app is so nothing surprising or
b) it is actually localized just happens to be a string that has characters that are all also English characters. In this case, presumably the user will be able to read it in their local language.
One final gotcha using the code/algorithm above: It turns out the English error messages that Apple uses don't stick to the normal ASCII character set. They will use the fancy apostrophe in Unicode as opposed to the ASCII "'". So, the code above will think that is a "localized" message. Unclear what other characters they do this with. So this isn't really a full solution. It does solve the original question of "How do I always retrieve an English error message", but it doesn't fully solve my problem since i have no idea how to build a font that will even show all English error messages. Sigh.
Still have to decide the best approach here, but it is something.

VoiceOver accessibility label for Touch ID

I am trying to ensure that the iOS app that I am working on is accessible and am trying to implement VoiceOver to ensure this.
One strange thing that I cannot find any help for is when the Touch ID view is displayed (in my case for signing into the app). VoiceOver pronounces ID as a word and not I.D.
I have tried implementing the accessibility attributes to both NSString and the LAContext object but neither seem to change what is read out by VoiceOver. Code snippets below:
LAContext *context = [[LAContext alloc] init];
[context setIsAccessibilityElement:YES];
[context setAccessibilityLabel:#"TEST 2"];
NSError *error = nil;
NSString *label = #"Please authenticate your ID using the Touch ID";
[label setIsAccessibilityElement:YES];
[label setAccessibilityTraits:UIAccessibilityTraitStaticText];
[label setAccessibilityLabel:#"TEST"];
showingTouchID = TRUE;
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:label
reply:^(BOOL success, NSError *error) {
......
The output from VoiceOver with or without context having the accessibility attributes is always the label text.
All help greatly appreciated :)
You should definitely not change the accessibility label just to make VoiceOver pronounce things correctly (i.e. do not try "hack" the label pronounciation). The reason is that VoiceOver does not have speech output only; it has also braille output where blind users expect to read things exactly letter-by-letter as they are written (i.e. see exactly all the spaces, capital/small letters, etc.) If you did e.g. write "I D" instead of "ID", then while VoiceOver would pronounce it perhaps correctly (in the specific version of iOS), blind users, after also reading "I D" on a braille display might think that that is how it is actually written and appear let's say non-professionally when they would then use this wrong spelling in written exchanges with other people.
The correct way to deal with this, albeit without giving you an immediate solution, is:
File a bug with Apple about pronounciation of the specific word with the specific voice in the specific language (e.g. "Expected pronounciation: [aj'di:]" vs. "Actual pronounciation: [id]")
File a bug with Apple to request the ability to customize pronunciation only (i.e. where you would leave the accessibility label intact and correct, but specify to the voice how it should pronounce certain part of the text), and where this customization could be done for each language individually by the translator translating the strings (because wrong pronunciation is language-specific) - also see the next point.
If you can reword, try different word than the problematic one (which seems not applicable in case of "Touch ID" which is a set term). But this is a hack too, as that solves only the English original and does not care about translations where the rewording might on the contrary potentially complicate the pronunciation.
Sorry for the bad news.
Finally, here, both on iOS 8.4.1 and iOS 9.0.2, VoiceOver with default US English iOS voice, at least on this webpage, pronounces "ID" in "Touch ID" as [ajdi:], not [id].
You can try this for a quick work around: Just give space between I and D
NSString *label = #"Please authenticate your ID using the Touch ID";
label.accessibilityLabel=#"Please authenticate your I D using the Touch I D";
Also please note that you can only set accessibility to UIElements and you cannot set it to general variables. It doesn't make sense to set accessibility label for LAContext and to NSString.
YOu need to set the accessibility label to UILabel or the element which you give the NSString to.
Starting with iOS 11, you can set the element's accessibilityAttributedLabel and use the UIAccessibilitySpeechAttributeIPANotation key (Swift: NSAttributedString.Key.accessibilitySpeechIPANotation) to specify the pronunciation for a range of the attributed string.
See "Speech Attributes for Attributed Strings" for other tools you can use to tweak how VoiceOver reads your text.

Convert NSString into executable statement

I am facing an issue with converting NSString into executable statement of objective C.
For example,
NSString *strColorAssembly = #"[UIColor redColor]";
Now, I need to convert this string into an executable code and pass to .color property.
This is just an example, I am trying to build a project in which everything will be dynamic, so It would be much helpful if anyone can provide me right direction to go ahead.
I'm not sure if you can programmatically accomplish that goal. If it were me I would implement a method that accepted an NSString (or an NSInteger) and return a UIColor.
- (UIColor *)colorDecode:(NSString *)colorPassed
{
if ([colorPassed isEqualToString #"red"])
{
return [UIColor redColor];
}
else if
.
.
.
}
OR
- (UIColor *)colorDecodewithInt:(NSUInteger)colorIndex
{
switch (colorIndex)
{
case (0)
{
return [UIColor redColor];
break;
}
case (1)
.
.
.
.
default
{
return <some default color>;
break;
}
}
}
EDIT:
I suppose you could also use an NSDictionary that consists of a collection of UIColor objects as well. The bottom line is that you're going to be restricted to a pre-defined set of colors in your compiled code unless you start playing with CGColor at which point you can start dynamically pass the RGB values.
It sounds like what you are looking for is the setValue:ForKey: method. Anything that conforms to the NSKeyValueCoding Protocol will have that option available. However, this will only work for the key part (i.e. the name of the property). iOS explicitly prohibits any modification to the distributed code. I would recommend using the keys as strings and writing some kind of interpreter for the rest of your data.
You can't do that on iOS, period. iOS prevents you from allocating memory pages that can both be written to and also executed.
The best you could manage is an interpreter that takes strings in, parses them, and attempts to map them to Objective-C calls (using NSClassFromString and NSSelectorFromString).
I would recommend not doing what you're trying to do, and instead use one of the many bindings from Cocoa to a dynamic language. I think the most popular ones bind Cocoa to Lua, so then you can write your Lua code and everything will be dynamic.

Efficient way to validate state and zip code

I'm looking for a easy way to validate state and zip code (U.S Only). Obviously there are many ways that this can be done. I have zip code:
- (BOOL)validateZipCode
{
bool returnValue = false;
NSString *zipcodeExpression = #"^[0-9]{5}(-/d{4})?$"; //U.S Zip ONLY!!!
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:zipcodeExpression options:0 error:NULL];
NSTextCheckingResult *match = [regex firstMatchInString:ZipCodeTextField.text options:0 range:NSMakeRange(0, [ZipCodeTextField.text length])];
if (match)
{
returnValue = true;
}
return returnValue;
}
Would it be common to just have a list of all the states and abbreviations and just compare the user input to check for a match? This just seems like a lot.
I guess I'm looking for alternatives. I also heard of some frameworks that when a user inputs a zip code, the city and state are then auto generated. Does anyone know of any alternatives like this?
I simple plist file in your app preloaded with the state names and abbreviations is trivial enough. Your regular expression will validate that a string represents a valid zip code.
If you need to actually verify that the zip code and the state match, that will take a lot more data.
A quick google search should reveal some web services you may be able to use to get a state from a zip code. Zip codes change (at least new ones added) on occasion. You may be able to provide a state/zip code database file in your app. But you will need a way to update the database once in a while without needing to submit an update of your app.
Update: A quick look at the Wikipedia Zip Code page reveals that some zip codes span more than one state.

Change app language in iOS without restarting the app

I have seems some apps can change the language internally within the app without the need of restarting the app, I am wondering how they are implemented.
For example, for us using NSLocalizedString, I know it is possible to set the language at runtime at main.m when your AppDelegate is not initialized, but once it is initialized (particularly your view controller is created), change it has not effect until the next restart
[[NSUserDefaults standardUserDefaults]
setObject:[NSMutableArray arrayWithObjects:language, nil]
forKey:#"AppleLanguages"];
Anyone have idea how those dynamic language change can be done without restarting the app?
There's some discussion of other approaches here, in particular a notification based approach:
iOS: How to change app language programmatically WITHOUT restarting the app?
In my view there are really three tasks here:
(1) re-localization of resources automatically loaded from nibs. (for example if you dynamically instantiate another custom UIView from a nib, the "old" language strings and settings (images, text direction) will still be loaded)
(2) re-localization of strings currently displayed on the screen.
(3) re-localization of strings inserted by the developer (you) in program code.
Let's start with (3). If you look for the definition you will notice that NSLocalizedString is a macro. So if you don't want to change existing code too much, you can probably solve the problem of (3) by creating a new header file. In that header file, #undef and then re-#define NSLocalizedString to pick the localized string from the appropriate place--not the one that iOS defaults to, but one that you keep track of in some global variable (e.g., in an app delegate ivar). If you don't want to redefine NSLocalizedString but you still make your own alternative , you should probably still #undef NSLocalizedString if you don't want future developers to accidentally call it instead of the macro you replace it with. Not an ideal solution, but maybe the most practical.
As for (1), if you haven't done your localization in Interface Builder, but rather you do it dynamically in viewDidLoad, etc., no problem. You can use the same behavior just discussed (i.e., the modified NSLocalizedString, etc.). Otherwise you can either (a) implement a notification system as described in the link above (complicated), or (b) consider moving localization from IB to viewDidLoad, or (c) try overriding initWithNibName: and swap out the object loaded with the old language resources, with one loaded with the new language resources. This was an approach mentioned by Mohamed at the very bottom of this discussion: http://learning-ios.blogspot.ca/2011/04/advance-localization-in-ios-apps.html. He claims it causes problems (viewDidLoad isn't called). Even if it doesn't work, trying it out might point you towards something that does.
Finally, (2) is presumably the easiest task: just remove and re-add the current view (or in some cases, just redraw it).
the idea is to write a new macro like NSLocalizedString which should check if to take the translation from another specific bundle or not.
The method 2 in this article explain exactly how to do it.
In this particular case, the author doesn't use a new macro, but directly set a custom class for [NSBundle mainBundle].
I hope that #holex will understand the problem reading this.
I'm always using this way, it works perfectly, it might help you as well.
you should set all the texts with NSLocalizableString(...) for the UI for the current language in the -viewWillAppear: method of your every UIViewController.
using this way you (I mean, the users) don't need to restart the application after changing the language of iOS in the Settings.
of course, I'm using the Apple's standard localisation architecture.
UPDATE on (24 Oct 2013)
I've experienced the –viewWillAppear: method won't be performed for the actual view when the application enters to foreground; to solve that issue I also commit the procedure (see above) when I receive UIApplicationWillEnterForegroundNotification notification in the view.
My implementation uses a class to change the language and access the current language bundle. It's an example so if you were to use different languages than I am then change the methods to use your exact language codes.
This class will access the preferred languages from NSLocale and take the first object which is the language being used.
#implementation OSLocalization
+ (NSBundle *)currentLanguageBundle
{
// Default language incase an unsupported language is found
NSString *language = #"en";
if ([NSLocale preferredLanguages].count) {
// Check first object to be of type "en","es" etc
// Codes seen by my eyes: "en-US","en","es-US","es" etc
NSString *letterCode = [[NSLocale preferredLanguages] objectAtIndex:0];
if ([letterCode rangeOfString:#"en"].location != NSNotFound) {
// English
language = #"en";
} else if ([letterCode rangeOfString:#"es"].location != NSNotFound) {
// Spanish
language = #"es";
} else if ([letterCode rangeOfString:#"fr"].location != NSNotFound) {
// French
language = #"fr";
} // Add more if needed
}
return [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:#"lproj"]];
}
/// Check if preferred language is English
+ (BOOL)isCurrentLanguageEnglish
{
if (![NSLocale preferredLanguages].count) {
// Just incase check for no items in array
return YES;
}
if ([[[NSLocale preferredLanguages] objectAtIndex:0] rangeOfString:#"en"].location == NSNotFound) {
// No letter code for english found
return NO;
} else {
// Tis English
return YES;
}
}
/* Swap language between English & Spanish
* Could send a string argument to directly pass the new language
*/
+ (void)changeCurrentLanguage
{
if ([self isCurrentLanguageEnglish]) {
[[NSUserDefaults standardUserDefaults] setObject:#[#"es"] forKey:#"AppleLanguages"];
} else {
[[NSUserDefaults standardUserDefaults] setObject:#[#"en"] forKey:#"AppleLanguages"];
}
}
#end
Use the class above to reference a string file / image / video / etc:
// Access a localized image
[[OSLocalization currentLanguageBundle] pathForResource:#"my_image_name.png" ofType:nil]
// Access a localized string from Localizable.strings file
NSLocalizedStringFromTableInBundle(#"StringKey", nil, [OSLocalization currentLanguageBundle], #"comment")
Change language in-line like below or update the "changeCurrentLanguage" method in the class above to take a string parameter referencing the new language code.
// Change the preferred language to Spanish
[[NSUserDefaults standardUserDefaults] setObject:#[#"es"] forKey:#"AppleLanguages"];
I was stuck in same issue, my requirement was "User can select language from drop down & application have to work according selected language (English or arabic)" What i have done i create two XIB and fetch XIB and Text according selected language. In this way user can select language. I used NSBundle for the same. like
For XIB
self.homeScreen = [[HomeScreen alloc] initWithNibName:#"HomeScreen" bundle:[CommonData sharedCommonData].languageBundle];
For Text
_lblHeading.text = [self languageSelectedStringForKey:#"ViewHeadingInfo"];
/**
This method is responsible for selecting language bundle according to user's selection.
#param: the string which is to be converted in selected language.
#return: the converted string.
#throws:
*/
-(NSString*) languageSelectedStringForKey:(NSString*) key
{
NSString* str=[[CommonData sharedCommonData].languageBundle localizedStringForKey:key value:#"" table:nil];
return str;
}
You need to load another bundle like this(where #"en" could be locale you need):
NSString *path = [[NSBundle mainBundle] pathForResource:#"en" ofType:#"lproj"];
NSBundle *languageBundle = [NSBundle bundleWithPath:path];
and make macros/function like NSLocalizedString which use your loaded bundle or use methods on that bundle directly like this
[languageBundle localizedStringForKey:key value:value table:tableName];
[[NSBundle mainBundle] localizations] lists all app localizations(including "Base").
Also I wrote helper class which does this(note that it has ReactiveCocoa as a dependency). It allows language change without app restart and sends current locale each time it's changed.

Resources