Last active
October 15, 2022 08:02
-
-
Save davedelong/2a2ae3c4682586278b14d0d5131fe050 to your computer and use it in GitHub Desktop.
Revisions
-
davedelong revised this gist
Oct 12, 2019 . 1 changed file with 3 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -62,7 +62,7 @@ + (NSLocale *)localeIncludingOverridesWithLocaleIdentifier:(NSString *)localeIde struct __DDLocale { void *_base; // The CF version of an "isa" pointer void *_field1; // maybe the locale identifier? void *_field2; // maybe a cache void *_field3; // no idea CFDictionaryRef _prefs; // The Secret Sauce @@ -82,10 +82,10 @@ + (NSLocale *)localeIncludingOverridesWithLocaleIdentifier:(NSString *)localeIde NSMutableDictionary *components = [[NSLocale componentsFromLocaleIdentifier:localeIdentifier] mutableCopy]; // So instead we'll "force" a new instance of a locale by creating a never-before-seen // locale identifier, thanks to the improbable magic of NSUUID [components setObject:[[NSUUID UUID] UUIDString] forKey:@"custom"]; // Turn the components back into a locale identifier. It's now "guaranteed" // to be unique NSString *newID = [NSLocale localeIdentifierFromComponents:components]; -
davedelong created this gist
Oct 12, 2019 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,114 @@ /* ====================================================== THIS CODE IS FOR EDUCATIONAL PURPOSES ONLY. I'M NOT RESPONSIBLE IF YOU SHIP THIS AND IT BLOWS UP IN YOUR FACE. IF IT DOES AND YOU COMPLAIN TO ME I WILL LAUGH AT YOU. ====================================================== This code is based on the following premise: • You want to show things in a particular locale • You want to honor any overrides the user has set iOS will only format dates using the +[NSLocale currentLocale] if the app is localized to support that locale. That means that if your app is running on: • a device set to a language your app does not support AND • the user has custom overrides for 12/24 hour, date/time formats, etc THEN: • the user will see dates formatted in another language, and WITHOUT their overrides. This is because you end up getting a locale that's just the "default" locale for a particular language, with no overrides applied. Enter this code. This code attempts to work around this (admittedly rather edge case) scenario. The overrides on a locale are stored inside the "_prefs" field. For the current locale, this is basically copied out of NSUserDefaults and is how the current locale knows about the 12/24 hour override, among other things. For custom locales, this is NULL. And there is no way to change it. Also working against us is the fact that _prefs IS NOT AN OBJC IVAR FIELD. NSLocale is toll-free bridged to CFLocaleRef, which means we're really dealing with pointers to C structs, and the fields on those structs are not described to the Objective-C runtime. So the Runtime doesn't even help us. So instead, we get to drop down to the raw memory layout and fudge things around. Please don't use this in shipping code. */ @interface NSLocale (Voodoo) // hide all the nastiness behind a nice, pleasant method + (NSLocale *)localeIncludingOverridesWithLocaleIdentifier:(NSString *)localeIdentifier; @end // this is the (rough) layout of a CFLocaleRef // I figured this out based on: // - https://github.com/apple/swift-corelibs-foundation/blob/155f1ce1965effe55289477507a6f9fbdc8fe333/CoreFoundation/Locale.subproj/CFLocale.c#L144-L151 // - too much time in the debugger struct __DDLocale { void *_base; // The CF version of an "isa" pointer void *_field1; // maybe the locale identifier? void *_field2; // maybe a cache void *_field3; // no idea CFDictionaryRef _prefs; // The Secret Sauce void *_lock; // maybe some thread safety thing Boolean _nullLocale; // who knows }; @implementation NSLocale (Voodoo) + (NSLocale *)localeIncludingOverridesWithLocaleIdentifier:(NSString *)localeIdentifier { // First, rip apart the locale identifier in to its components // Why? because if you create a locale w/ an identifier, and another locale instance // already exists that has the same identifier, you'll just get a pointer back // to the pre-existing object. For this class, that's probably what you want // most of the time. For this scenario, it's not. NSMutableDictionary *components = [[NSLocale componentsFromLocaleIdentifier:localeIdentifier] mutableCopy]; // So instead we'll "force" a new instance of a locale by creating a never-before-seen // locale identifier, thanks to the improbably magic of NSUUID [components setObject:[[NSUUID UUID] UUIDString] forKey:@"custom"]; // Turn the components back into a locale identifier. It's not "guaranteed" // to be unique NSString *newID = [NSLocale localeIdentifierFromComponents:components]; // Construct a brand-spanking-new NSLocale NSLocale *copy = [[NSLocale alloc] initWithLocaleIdentifier:newID]; // If, for some reason, it failed, early return // This is because we're about to do pointer magic, and doing pointer // magic with a NULL pointer is a great way to crash. Let's not crash. if (copy == nil) { return nil; } // Cast (lie to the compiler) that these pointers are actually pointers // to our struct definition that we worked out before struct __DDLocale* this = (struct __DDLocale *)[NSLocale currentLocale]; struct __DDLocale* that = (struct __DDLocale *)copy; // Copy over the dictionary of overrides from the +currentLocale // to our new copy. This is how our copy will get to know about the // overrides as well that->_prefs = CFDictionaryCreateMutableCopy(NULL, 0, this->_prefs); // Share And Enjoy. return copy; } @end