i​OS 9

Written by Nate Cook

WWDC 2015 may not have packed quite as many fireworks as its predecessor, but neither was it short on the new and shiny. The introduction of watchOS 2, including a revamped WatchKit, access to the Apple Watch hardware, and support for complications. Major changes to the Swift language and the announcement of open source plans. Brand new frameworks, such as Contacts, ContactsUI, and CoreSpotlight, and new UIKit types like UIStackView and SFSafariViewController.

We’ll surely delve into these major new components in the weeks and months to come. For now, however, let’s take a look at some of the smaller changes that iOS 9 brings to the APIs we already know and love.


String Transformations

Up first is the news that string transformations, formerly housed in Core Foundation, have made their way to NSString and the Swift String type. This is a huge advance in discoverability and ease-of-use for this powerful Cocoa feature, because there is no longer any need to deal with the hassle of bridging to and from CFStringRef. Here’s how some of our favorite transformations can be done along with the new NSStringTransform* constants that enable them:

Transliteration

"privet".stringByApplyingTransform(NSStringTransformLatinToCyrillic, reverse: false)
// "привет"
"안녕하세요".stringByApplyingTransform(NSStringTransformLatinToHangul, reverse: true)
// "annyeonghaseyo"
"annyeonghaseyo".stringByApplyingTransform(NSStringTransformLatinToHangul, reverse: false)
// "안녕하세요"
NSLog(@"%@", [@"privet" stringByApplyingTransform:NSStringTransformLatinToCyrillic reverse:NO]);
// "привет"
NSLog(@"%@", [@"annyeonghaseyo" stringByApplyingTransform:NSStringTransformLatinToHangul reverse:NO]);
// "안녕하세요"
NSLog(@"%@", [@"안녕하세요" stringByApplyingTransform:NSStringTransformLatinToHangul reverse:YES]);
// "annyeonghaseyo"

Unicode Names

"🐷".stringByApplyingTransform(NSStringTransformToUnicodeName, reverse: false)
// "{PIG FACE}"
NSLog(@"%@", [@"🐷" stringByApplyingTransform:NSStringTransformToUnicodeName reverse:NO]);
// "{PIG FACE}"

Normalizing User Input

"Hello! こんにちは! สวัสดี! مرحبا! 您好!"
        .stringByApplyingTransform(NSStringTransformToLatin, reverse: false)?
        .stringByApplyingTransform(NSStringTransformStripDiacritics, reverse: false)?
        .localizedLowercaseString
        .componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
// ["hello!", "kon'nichiha!", "swasdi!", "mrhba!", "nin", "hao!"]
NSString *input = @"Hello! こんにちは! สวัสดี! مرحبا! 您好!";
NSString *processing = [input stringByApplyingTransform:NSStringTransformToLatin reverse:NO];
processing = [processing stringByApplyingTransform:NSStringTransformStripDiacritics reverse:NO];

NSArray<NSString *> *output = [processing.localizedLowercaseString
                               componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSLog(@"%@", output);
// ["hello!", "kon'nichiha!", "swasdi!", "mrhba!", "nin", "hao!"]

For more on string transformations, be sure to check out Mattt’s terrific article on CFStringTransform.

CLLocationManager.requestLocation

Core Location includes a nice new API for apps that simply need to fetch the user’s location without continuous location updates. requestLocation() uses the same delegate methods as continuous updates, turning itself off after delivering the location with the desired accuracy:

class ViewController : UIViewController, CLLocationManagerDelegate {
    let locationManager = CLLocationManager()
    // ...

    override func viewDidLoad() {
        super.viewDidLoad()

        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
        locationManager.requestLocation()
    }

   // MARK: - CLLocationManagerDelegate

    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let location = locations.first {
            print("Current location: \(location)")
        } else {
            // ...
        }
    }

    func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
        print("Error finding location: \(error.localizedDescription)")
    }
}

Swiftification

The Cocoa APIs in iOS 9 (and OS X 10.11) include thousands of minor changes designed to make Swift interoperability safer and cleaner. These changes start with the familiar nullability annotations, converting implicitly unwrapped parameters and return values to either true Optionals or simple, non-optional data types. But they also go much further.

For example, instead of marking NSArray return values as nullable, many APIs have been modified to return an empty array—semantically these have the same value (i.e., nothing), but a non-optional array is far simpler to work with. Moreover, many Cocoa APIs are gradually taking advantage of the new Objective-C generic syntax to provide typed arrays. The CLLocationManagerDelegate method locationManager(_:didUpdateLocations:) shown above is one such example: where the locations parameter used to be imported as an AnyObject array, it is now an array of CLLocation, eliminating the need for any casting in the method body.

Finally, some APIs that were completely inaccessible from Swift have been revised, such as methods for retrieving UIAppearance proxies that limit appearance to a particular containment hierarchy—UIKit’s version of CSS-lite. The old appearanceWhenContainedIn: method is implemented with C variadic parameters, which Swift doesn’t import; the new appearanceWhenContainedInInstancesOfClasses(_:) method simply takes an array of type objects:

UIBarButtonItem.appearanceWhenContainedInInstancesOfClasses([UINavigationController.self]).tintColor = UIColor.redColor()
[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UINavigationController class]]].tintColor = [UIColor redColor];

Ironically enough, this method crashes at runtime when used from Swift. Betas gonna beta.

NSFormatter Additions

The new Contacts framework includes NSFormatter subclasses for localized formatting of contacts and addresses alongside a new Foundation NSPersonNameComponentsFormatter class. We’ll cover those more in the weeks to come, but here let’s highlight a few additions to two old favorites: NSNumberFormatter and NSDateFormatter.

NSNumberFormatter

First, NSNumberFormatter sees four additional styles in iOS 9, starting with .OrdinalStyle, used for converting numbers to their ordinal equivalent:

let formatter = NSNumberFormatter()
formatter.numberStyle = .OrdinalStyle

let numbers = [1, 2, 3, 4, 5]
numbers.map { formatter.stringFromNumber($0)! }
// ["1st", "2nd", "3rd", "4th", "5th"]

formatter.locale = NSLocale(localeIdentifier: "es")
numbers.map { formatter.stringFromNumber($0)! }
// ["1º", "2º", "3º", "4º", "5º"]
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterOrdinalStyle;
NSArray<NSNumber *> *numbers = @[@1, @2, @3, @4, @5];

for (NSNumber *number in numbers) {
    NSLog(@"%@", [formatter stringFromNumber:number]);
}
// "1st", "2nd", "3rd", "4th", "5th"

formatter.locale = [NSLocale localeWithLocaleIdentifier:@"es"];
for (NSNumber *number in numbers) {
    NSLog(@"%@", [formatter stringFromNumber:number]);
}
// "1º", "2º", "3º", "4º", "5º"

Next, the existing .CurrencyStyle gets some company with .CurrencyPluralStyle, .CurrencyISOCodeStyle, and .CurrencyAccountingStyle. When using these new styles, be sure your locale fully specifies both a language and a country, which it makes it possible for the formatter to choose the right currency and presentation:

let styles: [NSNumberFormatterStyle] = [.CurrencyStyle, .CurrencyPluralStyle, 
                .CurrencyISOCodeStyle, .CurrencyAccountingStyle]
formatter.locale = NSLocale(localeIdentifier: "en_US")
styles.map {
    formatter.numberStyle = $0
    return formatter.stringFromNumber(-125)!
}
// ["-$125.00", "-125.00 US dollars", "-USD125.00", "($125.00)"]

formatter.locale = NSLocale(localeIdentifier: "es_ES")
styles.map {
    formatter.numberStyle = $0
    return formatter.stringFromNumber(-125)!
}
// ["-125,00 €", "-125,00 euros", "-125,00 EUR", "-125,00 €"]

NSDateFormatter

Second—and I’ll fess up—this new NSDateFormatter method is a bit of a cheat. setLocalizedDateFormatFromTemplate(_:) was introduced in iOS 8 but largely slid under the radar until this year’s sessions on internationalization. This new-ish method makes it supremely easy to define a template for the date and time elements you want; however, it leaves the formatting up to the excellent localization built into NSDateFormatter:

let now = NSDate()

// full date and time
let fullFormatter = NSDateFormatter()
fullFormatter.setLocalizedDateFormatFromTemplate("yyyyMMMMddhhmm")
// month name and year only
let shortFormatter = NSDateFormatter()
shortFormatter.setLocalizedDateFormatFromTemplate("yyMMMM")

fullFormatter.stringFromDate(now)
// "June 23, 2015, 4:56 PM"
shortFormatter.stringFromDate(now)
// "June 15"

If you switch locales, you’ll need to reset the localized date format to get the correct template for the new locale:

fullFormatter.locale = NSLocale(localeIdentifier: "de_DE")
fullFormatter.setLocalizedDateFormatFromTemplate("yyyyMMMMddhhmm")

shortFormatter.locale = NSLocale(localeIdentifier: "de_DE")
shortFormatter.setLocalizedDateFormatFromTemplate("yyMMMM")

fullFormatter.stringFromDate(now)
// "23. Juni 2015, 4:56 nachm."
shortFormatter.stringFromDate(now)
// "Juni 15"

Well, that’s that for our quick tour through some of iOS 9’s new APIs, though there are many more where those came from. What new features are you most excited about in iOS 9 or OS X 10.11? Check out Apple’s diffs and let us know!

NSMutableHipster

Questions? Corrections? Issues and pull requests are always welcome — NSHipster is made better by readers like you.

This article uses Swift version 2.0. Find status information for all articles on the status page.

Follow NSHipster
Next Article

As an iOS developer, if you want to make an application on your own, you sometimes need to write back-end code. Even for the developer who can take that on, there is more than just the code, there’s also maintenance. Your worst fear becomes not that people might not like your application, but that your server might fail under heavy traffic.

Fortunately, we now have CloudKit. Apple takes care of all these details, so you can focus on how to make your application great.