I’ve been listening to The History of English Podcast. It’s not easy to binge but it’s a mainstay in my weekly podcast rotation. I’m currently around the fall of the Roman Empire. It’s fascinating that our alphabet has bounced between cultures but with only minor changes.

Localizing attributed strings on iOS

In an iOS app, localization can be especially difficult when dealing with attributed strings. Fairly often, designers request something like:

Read our Terms of Service, Privacy Policy, or contact us with any questions.

or like:

Searching for burgers in SOMA, San Francisco, CA:

The golden rule of localized strings is to treat them as atomic units:

  • Never concatenate strings to form sentences. Many languages have different sentence structure or gender rules than English and you cannot just substitute a single word or phrase in for any other.
  • Never use substrings for searching in an original string. If you depend on the standalone translation of “Privacy Policy” matching the sentence version, you will likely find translators do not understand this intent. You may also find the same search term multiple times (imagine if the user searched for “Search”).

Often these complexities are cited as reasons to avoid localization. But, unless you have geographic constraints, you will find a substantially larger audience with a localized application.

ZSWTappableLabel and ZSWTaggedString are two open-source libraries I have released to help solve these problems.

ZSWTappableLabel makes links inside your attributed strings tappable, as the name suggests. It’s a UILabel subclass which does not do any drawing itself, making it fast and easy.

ZSWTaggedString is the powerhouse. It transforms an HTML-like syntax into an attributed string. You can read more about the syntax and advanced usage on its GitHub page, but here’s how you might use it for the examples above:

Read our <i><tos>Terms of Service</tos></i>, <i><privacy>Privacy Policy</privacy></i>, or <i><contact>contact us</contact></i> with any questions.

Searching for <term>%@</term> in <location>%@</location>:

In my experience, localizers1 are familiar enough with HTML to have no issues with localizing these strings. By marking the regions you intend to be visually distinct, they can more easily understand your intent, producing better localizations.

While on the subject, here are a few best practices for localization in iOS:

  • To handle the current locale changing, or the dynamic type setting changing, reload your UI when observing:
    • NSCurrentLocaleDidChangeNotification
    • UIContentSizeCategoryDidChangeNotification
  • To represent dates, durations, distances, lengths, etc., use an appropriate formatter.
  • To create your own date formats, use +dateFormatFromTemplate:options:locale: on NSDateFormatter. Remember that these need recreating if the locale changes.
  • To combine a first and last name, use ABPersonGetCompositeNameFormatForRecord with a temporary ABPersonRef, or use the new NSPersonNameComponentsFormatter.
  • For sending non-user-facing data to a server, use en_US_POSIX as your locale.

    Read more tips and tricks at NSHipster about NSLocalizedString and NSLocale.


  1. If you’re looking for recommendations on localization, working with Applingua has always been wonderful. 

T-Mobile: good ideas, bad experience

When T-Mobile entered the wireless scene as the “Uncarrier” I was impressed. Their greatest contribution to the carrier ecosystem is consistently adding features, forcing other carriers to keep up. Instead of rationed text and voice, we’re in a world where data is king.

However, if you are considering T-Mobile service, I suggest reconsidering. The plans and features look appetizing, but their execution leaves a lot to be desired. I would not count on their unique features; just the now-basics with worse coverage.

I wrote off my initial experiences as anecdotal, but it became cumulatively enough for me to leave their service.

Continue reading T-Mobile: good ideas, bad experience

Saving optimal JPEGs on iOS

Conventional wisdom for creating a JPEG version of a UIImage is first to turn it into an NSData and immediately write it to disk like so:

NSData *jpegRepresentation = UIImageJPEGRepresentation(image, 0.94);
[jpegRepresentation writeToFile:outputURL.path
                     atomically:NO];

Most of the time this is exactly right. However, if file size is important, Image IO is a great alternative. It is a powerful system framework to read and write images, and produces smaller files at the same compression level.

Why Image IO?

A project I am working on requires uploading photos en masse. Low upload bandwidth makes file size a limiting factor, so I sought out ways to reduce it.

I put together a test project to find the differences between the two methods. The results are pretty interesting:

  • Image IO files are on average 20% (but up to 30%) smaller1.
  • Image IO takes about 2x longer.

The only discernable visual difference is the grain in the images, but even that is minor. Here’s a diff between two versions of the original photo. The changes are nearly all in the grain.

A diff between the two image output types

Using Image IO

First, you’ll need to add two new framework dependencies:

@import ImageIO; // to do the actual work
@import MobileCoreServices; // for the type defines

When creating your JPEG file, the first step is to create a CGImageDestinationRef specifying where to write the result:

CGImageDestinationRef destinationRef = 
CGImageDestinationCreateWithURL((__bridge CFURLRef)outputURL,
                                /* file type */ kUTTypeJPEG,
                                /* number of images */ 1,
                                /* reserved */ NULL);

Image IO is able to produce files of a few different types2 but my focus here is JPEGs. Next, we set up the properties of the output file, specifying a constant compression factor:

NSDictionary *properties = @{
  (__bridge NSString *)kCGImageDestinationLossyCompressionQuality: @(0.94)
};

CGImageDestinationSetProperties(destinationRef,
                                (__bridge CFDictionaryRef)properties);

And, importantly, we specify what is to be written out:

CGImageDestinationAddImage(destinationRef,
                           /* image */ image.CGImage,
                           /* properties */ NULL);

And finally, we write it to disk and clean up the reference:

CGImageDestinationFinalize(destinationRef);
CFRelease(destinationRef);

  1. The UIImage version has a color profile, while the Image IO version does not. However, running both files through Image Optim produces a 7% reduction on both, so I am choosing to ignore this difference. Afterall, you can’t remove the color profile anyway! 
  2. The following are possible types you can use, from the documentation:

    Constant UTI type
    kUTTypeImage public.image
    kUTTypePNG public.png
    kUTTypeJPEG public.jpeg
    kUTTypeJPEG2000 public.jpeg-2000 (OS X only)
    kUTTypeTIFF public.tiff
    kUTTypePICT com.apple.pict (OS X only)
    kUTTypeGIF com.compuserve.gif