IOS Upgrade to 7.0 Issue With Dictionary Lookup - ios

Using location Manager in an app the following code worked prior to iOS7, now with iOS7, I'm getting the "??" escape. I'm looking up the state to retrieve the state abbreviation. The location mananger is properly retrieving the State (if I code to use "state" it will give me the desired state), but the lookup to the plist file (set to dictionary object) to get the abbreviation, for whatever reason fails and gives the "??" option. Any Ideas what's up?
NSString *state = placemark.administrativeArea;
NSString *stateAbbreviation = [self.usStateAbbreviations objectForKey:[state uppercaseString]];
NSString *stateTarget = state;
if (stateAbbreviation) {
stateTarget = stateAbbreviation;
}else{
stateTarget = #"??";
}

From another question:
For iOS6 i get the full name of the administrative area (ex.
"California"), but for the iOS7, I get the value of "CA".
So, it would seem, that state is already the stateAbbreviation on iOS7, so the key is different and you don't get a result for:
[self.usStateAbbreviations objectForKey:[state uppercaseString]];
According to Apple's documentation for CLPlacemark:
The string in this property can be either the spelled out name of the
administrative area or its designated abbreviation, if one exists. If
the placemark location is Apple’s headquarters, for example, the value
for this property would be the string “CA” or “California”.
So there doesn't seem to be a guarantee, one way or the other.

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.

CLPeripheralManager.startAdvertising does not accept return value of CLBeaconRegion.peripheralDataWithMeasuredPower

According to the Swift 2.0 documentation for CLBeaconRegion, it should still be possible to pass the output of the peripheralDataWithMeasuredPower: method to the startAdvertising: method of CLPeripheralManager.
Getting Beacon Advertisement Data
- peripheralDataWithMeasuredPower:
Retrieves data that can be used to advertise the current device as a beacon.
Declaration
SWIFT
func peripheralDataWithMeasuredPower(_measuredPower: NSNumber?) -> NSMutableDictionary
OBJECTIVE-C
- (NSMutableDictionary<NSString *,id> * _Nonnull)peripheralDataWithMeasuredPower:(NSNumber * _Nullable)measuredPower
Parameters
measuredPower The received signal strength indicator (RSSI) value (measured in decibels) for the device.
This value represents the measured strength of the beacon from one
meter away and is used during ranging. Specify nil to use the default
value for the device.
Return Value
A dictionary of data that you can
use in conjunction with a CBPeripheralManager to advertise the current
device as a beacon.
Discussion
The returned dictionary encodes the beacon’s identifying
information along with other information needed to advertise the
beacon. You should not need to access the dictionary contents
directly. Pass the dictionary to the startAdvertising: method of a
CBPeripheralManager to begin advertising the beacon.
Availability
Available in iOS 7.0 and later.
However, peripheralDataWithMeasuredPower: returns an NSMutableDictionary whereas the startAdvertising: method of CLPeripheralManager accepts a Swift Dictionary of [String : AnyObject]?, although the documentation contends that it accepts an NSDictionary. The following code that worked in Swift 1.0:
// Set up a beacon region with the UUID, Major and Minor values
let region = CLBeaconRegion(proximityUUID:beaconUUID!, major:withMajor.unsignedShortValue, minor:withMinor.unsignedShortValue, identifier:"com.example.beacon")
// Attempt to set up a peripheral with the measured power
let peripheralData = region.peripheralDataWithMeasuredPower(withPower)
_peripheralManager!.startAdvertising(peripheralData)
In Swift 2.0 the same code fails to compile with a warning:
fails to compile, with a warning:
NSDictionary is not convertible to [String : AnyObject]; did you
mean to use as! to force downcast?
However, forcing a downcast always fails.
Is this a documentation bug, a bug in Swift 2.0, or am I missing something?
The problem seems to be that NSMutableDictionary is not easily convertible to Swift's Dictionary, but NSDictinoary is. So I ended up converting the NSMutableDictionary to a NSDictinoary first:
let pd = NSDictionary(dictionary: region.peripheralDataWithMeasuredPower(nil))
as! [String: AnyObject]
peripheralManager.startAdvertising(pd)
And it works!

Value stored to NSString during its initialization is never read

In my iOS app I have following code:
case SASpeechSubCase03:
{
SAActivity currentActivity = self.mediator.selectedActivity;
NSString *sActivity = NSLocalizedString(#"activity", #"activity");
NSString *sActivity2 = NSLocalizedString(#"another activity", #"another activity");
if(currentActivity == SAActivityWalk)
{
sActivity = NSLocalizedString(#"walk", #"walk");
sActivity2 = NSLocalizedString(#"walking", #"walking");
}
else
{
sActivity = NSLocalizedString(#"run", #"run");
sActivity2 = NSLocalizedString(#"jogging", #"jogging");
}
return [NSString stringWithFormat:speech.text, sActivity, sActivity2];
break;
}
When I run bots on it, it gave me following warning:
Bot Issue: analyzerWarning. Dead store.
Issue: Value stored to 'sActivity' during its initialization is never read.
File: SAAnnouncementService.m.
Integration Number: 42.
Description: Value stored to 'sActivity' during its initialization is never read.
Bot Issue: analyzerWarning. Dead store.
Issue: Value stored to 'sActivity2' during its initialization is never read.
File: SAAnnouncementService.m.
Integration Number: 42.
Description: Value stored to 'sActivity2' during its initialization is never read.
Can someone tell what the problem might be here?
Any kind of help is highly appreciated!
The problem is that you initialized the variables and then directly started the if-else blocks, without using, i.e. reading, the initial values.
When execution gets to the if-else blocks, it will definitely be assigned a new value, no matter what value it was before.
With the following line :
NSString *sActivity = NSLocalizedString(#"activity", #"activity");
NSString *sActivity2 = NSLocalizedString(#"another activity", #"another activity");
You are assigning string values to the sActivity and sActivity2 objects.
Then, these two values are modified in either if or else statement.
But, as the static analyzer mentions, the initial values of these objects (#"activity" and #"another activity") were never read before the second assignment (in if / else statement).
To avoid this warning you can replace the two lines above, by :
NSString *sActivity = nil;
NSString *sActivity2 = nil;
Hope that helps ;)
When you get a warning, the compiler tells you "what you are doing here looks like nonsense, and is most likely not what you want".
Look at these two statements:
NSString *sActivity = NSLocalizedString(#"activity", #"activity");
NSString *sActivity2 = NSLocalizedString(#"another activity", #"another activity");
Does the assignment serve any purpose? It doesn't look like it. So the compiler thinks "either the guy made a rather expensive call that is completely pointless, or he actually intended to use the result of NSLocalizedString but stored it in the wrong place. "
Since the compiler assumes that people don't do pointless things, it assumes that there is a bug in your code and tells you about it. It's the kind of thing where a human reviewing your code would stop and ask you what you were intending to do there.
In your codes, sActivity would be set to either walk or run within IF/ELSE, so that the value set for sActivity this line
NSString *sActivity = NSLocalizedString(#"activity", #"activity");
would never be read. It might not cause error but analyzer reminded you about this superfluous initialization. Try NSString *sActivity=nil;, see if the warning could be turned down.
You are not using sActivity in if-else blocks, you are simply assigning it values based on decision, So either take it nil string like
sActivity = nil;
or like
NSString *sActivity;
to remove waring .

Core Data predicate not matching boolean attribute

I have an NSManagedObject subclass with a boolean attribute. It's non-optional, with NO as the default. In the model interface it is declared as #property (nonatomic, retain) NSNumber * deleted;, in the implementation, #dynamic deleted;.
It is persisting correctly if I check the underlying SQLite file. However, I am finding that fetches with predicates querying this attribute do not work correctly. I have tried deleted == YES, deleted == %# with #YES and [NSNumber numberWithBool:YES], and indeed deleted == 1. I've even tried using a single equals sign out of pure voodoo paranoia. Nothing works.
This is causing a bug in my code using an NSFetchedResultsController. The full predicate is currently (list = %#) OR (deleted = YES). I change deleted to #YES, change the value of list, and the controller issues an unexpected NSFetchedResultsChangeDelete, despite the object still logically matching the predicate.
There's no doubting it doesn't match the predicate however (at least in memory), as I've tested with:
BOOL matchesBefore = [self.fetchedResultsController.fetchRequest.predicate evaluateWithObject:thing];
// do stuff, setting thing.deleted = #YES, thing.list = #"something else"
BOOL matchesAfter = [self.fetchedResultsController.fetchRequest.predicate evaluateWithObject:thing];
NSAssert(matchesBefore && matchesAfter, #"Should still match");
Interestingly, in the debugger, the attribute is displayed as deleted = 0;, however printing the actual NSNumber yields (NSNumber *) $5 = 0x07455ec0 1. Again, in the underlying database, the value is stored correctly as '1'.
So I'm greatly confused. Any ideas? This is iOS, on 5.x both in the simulator and on device.
Sounds like a collision with reserved names, some deep lying undocumented (?) use of the keyword deleted. (Wouldn't be strange in the context of a NSManagedObject, or maybe in NSFetchedResultsController.) Try to change the name of the field to something else.

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