iOS 9
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 UIStack and SFSafari.
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 CFString. Here’s how some of our favorite transformations can be done along with the new NSString 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 CFString.
CLLocationManager.requestLocation
Core Location includes a nice new API for apps that simply need to fetch the user’s location without continuous location updates. request 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 CLLocation method location shown above is one such example: where the locations parameter used to be imported as an Any 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 appearance method is implemented with C variadic parameters, which Swift doesn’t import; the new appearance method simply takes an array of type objects:
UIBarButtonItem.appearanceWhenContainedInInstancesOfClasses([UINavigationController.self]).tintColor = UIColor.redColor()
[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UINavigationController class]]].tintColor = [UIColor redColor];
NSFormatter Additions
The new Contacts framework includes NSFormatter subclasses for localized formatting of contacts and addresses alongside a new Foundation NSPerson class. We’ll cover those more in the weeks to come, but here let’s highlight a few additions to two old favorites: NSNumber and NSDate.
NSNumberFormatter
First, NSNumber sees four additional styles in iOS 9, starting with .Ordinal, 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 .Currency gets some company with .Currency, .Currency, and .Currency. 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 NSDate method is a bit of a cheat. set 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 NSDate:
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!