iOS 8
Ask anyone, and they’ll tell you: WWDC 2014 was one of the most exciting in recent memory. It was, first and foremost, a developer event, with nary a hardware announcement to upstage the latest software & developer tools.
And boy howdy, was there a lot to be excited about.
The announcements from iOS 8 & OS X Yosemite alone would have made 2014 a bellwether year for the Apple platform, with Extensions, Continuity, SpriteKit enhancements, SceneKit for iOS, Metal, Game HealthKit, HomeKit, Local Authentication, and a brand new Photos framework. Not to mention the dramatic improvements to Xcode & Interface Builder, a revamped iTunes Connect, TestFlight, Crash Reports, and CloudKit. And oh yeah—Swift.
The kicker? Apple has graciously relaxed its NDA for new technologies, meaning that we don’t have to wait to talk about all of the shiny new toys we have to play with.
This week, we’ll take a look beneath the headline features, and share some of the more obscure APIs that everyone should know about.
From here on out, NSHipster will primarily write code samples in Swift, with the occasional Objective-C throwback where appropriate. By the end of the summer, we hope to have all of the existing code samples ported to Swift, with the option to toggle between languages.
NSProcessInfo -isOperatingSystemAtLeastVersion
Forget [[UIDevice current
and NSFoundation
, there’s a new way to determine the current operating system in code: NSProcess
import Foundation
let yosemite = NSOperating System Version(major Version: 10, minor Version: 10, patch Version: 0)
NSProcess Info().is Operating System At Least Version(yosemite) // false
Keep in mind, however, that a test for capability, such as with Some
or responds
, is preferable to checking the OS version. Compiler macros in C or Swift can be used to conditionally compile source based on the build configuration of the target.
New NSFormatter Subclasses
One of the features most sorely lacking in Foundation was the ability to work with units for quantities like mass or length. In iOS 8 and OS X Yosemite, three new classes were introduced that fills the gap: NSEnergy
, NSMass
, & NSLength
.
This effectively doubles the number of
NSFormatter
subclasses in Foundation, which was previously limited toNSNumber
,Formatter NSDate
, &Formatter NSByte
.Count Formatter
Although these new formatter classes are part of Foundation, they were added primarily for use in HealthKit.
NSEnergyFormatter
NSEnergy
formats energy in Joules, the raw unit of work for exercises, and Calories, which is used when working with nutrition information.
let energy Formatter = NSEnergy Formatter()
energy Formatter.for Food Energy Use = true
let joules = 10_000.0
print(energy Formatter.string From Joules(joules)) // "2.39 Cal"
NSMassFormatter
Although the fundamental unit of physical existence, mass is pretty much relegated to tracking the weight of users in HealthKit. Yes, mass and weight are different, but this is programming, not science class, so stop being pedantic.
let mass Formatter = NSMass Formatter()
let kilograms = 60.0
print(mass Formatter.string From Kilograms(kilograms)) // "132 lb"
NSLengthFormatter
Rounding out the new NSFormatter
subclasses is NSLength
. Think of it as a more useful version of MKDistance
, with more unit options and formatting options.
let length Formatter = NSLength Formatter()
let meters = 5_000.0
print(length Formatter.string From Meters(meters)) // "3.107 mi"
CMPedometer
Continuing on iOS 8’s health kick, CMStep
is revamped in the latest release. CMPedometer
is a strict improvement over its predecessor, with the ability to query from discrete points in time, track both steps and distance, and even calculate how many flights of stairs were climbed.
It’s amazing what that M7 chip is capable of.
import Core Motion
let length Formatter = NSLength Formatter()
let pedometer = CMPedometer()
pedometer.start Pedometer Updates From Date(NSDate()) { data, error in
if let data = data {
print("Steps Taken: \(data.number Of Steps)")
if let distance = data.distance?.double Value {
print("Distance: \(length Formatter.string From Meters(distance))")
let time = data.end Date.time Interval Since Date(data.start Date)
let speed = distance / time
print("Speed: \(length Formatter.string From Meters(speed)) / s")
}
}
}
CMAltimeter
On supported devices, a CMPedometer
’s stats on floors
/ floors
can be augmented with CMAltimeter
to get a more granular look at vertical distance traveled:
import Core Motion
let altimeter = CMAltimeter()
if CMAltimeter.is Relative Altitude Available() {
altimeter.start Relative Altitude Updates To Queue(NSOperation Queue.main Queue()) { data, error in
if let data = data {
print("Relative Altitude: \(data.relative Altitude)")
}
}
}
CLFloor
CLFloor
is a new API in iOS 8 that ties the new features in CoreMotion with Apple’s ambitious plan to map the interiors of the largest buildings in the world. Look for this information to play a significant role in future hyperlocal mapping applications.
import Core Location
class Location Manager Delegate: NSObject, CLLocation Manager Delegate {
func location Manager(manager: CLLocation Manager, did Update Locations locations: [CLLocation]) {
if let floor = locations.first?.floor {
print("Current Floor: \(floor.level)")
}
}
}
let manager = CLLocation Manager()
manager.delegate = Location Manager Delegate()
manager.start Updating Location()
HKStatistics
As a framework, HealthKit covers a lot of ground, with dozens of new classes and constants. A good place to start, in terms of understanding what’s possible is HKStatistics
.
HealthKit manages your biometrics from all of your devices in a single unified API. Statistics on things like heart rate, caloric intake, and aerobic output can be tracked and aggregated in powerful ways.
The following example shows how statistics summed over the duration of the day can be grouped and interpreted individually:
import Health Kit
let collection: HKStatistics Collection? = ...
let statistics: HKStatistics? = collection?.statistics For Date(NSDate())
let sources: [HKSource] = statistics?.sources ?? []
for source in sources {
if let quantity = statistics?.sum Quantity For Source(source) {
if quantity.is Compatible With Unit(HKUnit.gram Unit With Metric Prefix(.Kilo)) {
let mass Formatter = NSMass Formatter()
let kilograms = quantity.double Value For Unit(HKUnit.gram Unit With Metric Prefix(.Kilo))
print(mass Formatter.string From Kilograms(kilograms))
}
if quantity.is Compatible With Unit(HKUnit.meter Unit()) {
let length Formatter = NSLength Formatter()
let meters = quantity.double Value For Unit(HKUnit.meter Unit())
print(length Formatter.string From Meters(meters))
}
if quantity.is Compatible With Unit(HKUnit.joule Unit()) {
let energy Formatter = NSEnergy Formatter()
let joules = quantity.double Value For Unit(HKUnit.joule Unit())
print(energy Formatter.string From Joules(joules))
}
}
}
NSHipster will be covering a lot more about HealthKit in future editions, so stay tuned!
NSStream +getStreamsToHostWithName
In many ways, WWDC 2014 was the year that Apple fixed their shit. Small things, like adding the missing NSStream
initializer for creating a bound stream pair (without resorting to awkwardly-bridged CFStream
call). Behold: +[NSStream get
var input Stream: NSInput Stream?
var output Stream: NSOutput Stream?
NSStream.get Streams To Host With Name("nshipster.com",
port: 5432,
input Stream: &input Stream,
output Stream: &output Stream)
NSString -localizedCaseInsensitiveContainsString
Also filed under: “small but solid fixes”, is this convenience method for String
/NSString
:
let string = "Café"
let substring = "É"
string.localized Case Insensitive Contains String(substring) // true
CTRubyAnnotationRef
If you’re a linguistics and typography nerd, this new addition to the CoreText framework may have you standing up on your chair and cheering. “What’s with Jim? Is it me, or has he been acting kind of weird since his trip to San Francisco?”, they’ll say, looking at you atop your desk as you tear your clothes off your body in a frenzy of pure ecstasy. “Yeah, remind me not to stay in the Tenderloin for next year’s conference.”
…oh right. Ruby. No, not Ruby. Ruby. It’s used to display the pronunciation of characters in certain Asian scripts.
@import Core Text;
NSString *kanji = @"猫";
NSString *hiragana = @"ねこ";
CFString Ref furigana[k CTRuby Position Count] =
{(__bridge CFString Ref)hiragana, NULL, NULL, NULL};
CTRuby Annotation Ref ruby =
CTRuby Annotation Create(k CTRuby Alignment Auto, k CTRuby Overhang Auto, 0.5, furigana);
Admittedly, the documentation isn’t entirely clear on how exactly to incorporate this into the rest of your Core
drawing calls, but the result would look something like this: