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 currentDevice] systemVersion] and NSFoundationVersionNumber, there’s a new way to determine the current operating system in code: NSProcessInfo -isOperatingSystemAtLeastVersion

import Foundation

let yosemite = NSOperatingSystemVersion(majorVersion: 10, minorVersion: 10, patchVersion: 0)
NSProcessInfo().isOperatingSystemAtLeastVersion(yosemite) // false

Keep in mind, however, that a test for capability, such as with SomeClass.class or respondsToSelector:, 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: NSEnergyFormatter, NSMassFormatter, & NSLengthFormatter.

This effectively doubles the number of NSFormatter subclasses in Foundation, which was previously limited to NSNumberFormatter, NSDateFormatter, & NSByteCountFormatter.

Although these new formatter classes are part of Foundation, they were added primarily for use in HealthKit.

NSEnergyFormatter

NSEnergyFormatter formats energy in Joules, the raw unit of work for exercises, and Calories, which is used when working with nutrition information.

let energyFormatter = NSEnergyFormatter()
energyFormatter.forFoodEnergyUse = true

let joules = 10_000.0
print(energyFormatter.stringFromJoules(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 massFormatter = NSMassFormatter()
let kilograms = 60.0
print(massFormatter.stringFromKilograms(kilograms)) // "132 lb"

NSLengthFormatter

Rounding out the new NSFormatter subclasses is NSLengthFormatter. Think of it as a more useful version of MKDistanceFormatter, with more unit options and formatting options.

let lengthFormatter = NSLengthFormatter()
let meters = 5_000.0
print(lengthFormatter.stringFromMeters(meters)) // "3.107 mi"

CMPedometer

Continuing on iOS 8’s health kick, CMStepCounter 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 CoreMotion

let lengthFormatter = NSLengthFormatter()
let pedometer = CMPedometer()
pedometer.startPedometerUpdatesFromDate(NSDate()) { data, error in
    if let data = data {
        print("Steps Taken: \(data.numberOfSteps)")

        if let distance = data.distance?.doubleValue {
            print("Distance: \(lengthFormatter.stringFromMeters(distance))")

            let time = data.endDate.timeIntervalSinceDate(data.startDate)
            let speed = distance / time
            print("Speed: \(lengthFormatter.stringFromMeters(speed)) / s")
        }
    }
}

CMAltimeter

On supported devices, a CMPedometer’s stats on floorsAscended / floorsDescended can be augmented with CMAltimeter to get a more granular look at vertical distance traveled:

import CoreMotion

let altimeter = CMAltimeter()
if CMAltimeter.isRelativeAltitudeAvailable() {
    altimeter.startRelativeAltitudeUpdatesToQueue(NSOperationQueue.mainQueue()) { data, error in
        if let data = data {
            print("Relative Altitude: \(data.relativeAltitude)")
        }
    }
}

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 CoreLocation

class LocationManagerDelegate: NSObject, CLLocationManagerDelegate {
    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let floor = locations.first?.floor {
            print("Current Floor: \(floor.level)")
        }
    }
}

let manager = CLLocationManager()
manager.delegate = LocationManagerDelegate()
manager.startUpdatingLocation()

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 HealthKit

let collection: HKStatisticsCollection? = ...
let statistics: HKStatistics? = collection?.statisticsForDate(NSDate())
let sources: [HKSource] = statistics?.sources ?? []

for source in sources {
    if let quantity = statistics?.sumQuantityForSource(source) {
        if quantity.isCompatibleWithUnit(HKUnit.gramUnitWithMetricPrefix(.Kilo)) {
            let massFormatter = NSMassFormatter()
            let kilograms = quantity.doubleValueForUnit(HKUnit.gramUnitWithMetricPrefix(.Kilo))
            print(massFormatter.stringFromKilograms(kilograms))
        }
        
        if quantity.isCompatibleWithUnit(HKUnit.meterUnit()) {
            let lengthFormatter = NSLengthFormatter()
            let meters = quantity.doubleValueForUnit(HKUnit.meterUnit())
            print(lengthFormatter.stringFromMeters(meters))
        }
        
        if quantity.isCompatibleWithUnit(HKUnit.jouleUnit()) {
            let energyFormatter = NSEnergyFormatter()
            let joules = quantity.doubleValueForUnit(HKUnit.jouleUnit())
            print(energyFormatter.stringFromJoules(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 CFStreamCreatePairWithSocketToHost call). Behold: +[NSStream getStreamsToHostWithName:port:inputStream:outputStream:]

var inputStream: NSInputStream?
var outputStream: NSOutputStream?

NSStream.getStreamsToHostWithName("nshipster.com",
                            port: 5432,
                     inputStream: &inputStream,
                    outputStream: &outputStream)

NSString -localizedCaseInsensitiveContainsString

Also filed under: “small but solid fixes”, is this convenience method for String/NSString:

let string = "Café"
let substring = "É"

string.localizedCaseInsensitiveContainsString(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 CoreText;

NSString *kanji = @"猫";
NSString *hiragana = @"ねこ";

CFStringRef furigana[kCTRubyPositionCount] =
    {(__bridge CFStringRef)hiragana, NULL, NULL, NULL};

CTRubyAnnotationRef ruby =
    CTRubyAnnotationCreate(kCTRubyAlignmentAuto, kCTRubyOverhangAuto, 0.5, furigana);

Admittedly, the documentation isn’t entirely clear on how exactly to incorporate this into the rest of your CoreText drawing calls, but the result would look something like this:

ねこ</figure>

New Calendar Identifiers

What’s even nerdier than Ruby annotations? The new calendar identifiers added to iOS 8 & OS X Yosemite. This update brings Foundation up to the latest version of the CLDR:

(Sadly, the French Republican Calendar is still but a twinkle in the eyes of NSHipsters everywhere)

  • NSCalendarIdentifierCoptic: a.k.a Alexandrian calendar, is used by the Coptic Orthodox Church.
  • NSCalendarIdentifierEthiopicAmeteMihret: Ethiopic calendar, Amete Mihret (epoch approx. 8 C.E.)
  • NSCalendarIdentifierEthiopicAmeteAlem: Ethiopic calendar, Amete Alem (epoch approx. 5493 B.C.E.)
  • NSCalendarIdentifierIslamicTabular: A simple tabular Islamic calendar using the astronomical/Thursday epoch of CE 622 July 15.
  • NSCalendarIdentifierIslamicUmmAlQura: The Islamic Umm al-Qura calendar used in Saudi Arabia. This is based on astronomical calculation, instead of tabular behavior.

NSURLCredentialStorage

The Foundation URL Loading System has remained relatively unchanged since last year’s NSURLSession blowout. However, NSURLCredentialStorage has been given some TLC, with new functions that get and set credentials for tasks in asynchronous, non-blocking fashion.

import Foundation

let session = NSURLSession()
let task = session.dataTaskWithURL(NSURL(string: "https://nshipster.com")!) { data, response, error in
    
}

let protectionSpace = NSURLProtectionSpace()
let credentialStorage = NSURLCredentialStorage()
credentialStorage.getCredentialsForProtectionSpace(protectionSpace, task: task) { credentials in
    
}

kUTTypeToDoItem

Looking through the latest API diffs, one might notice the large number of new UTIs constants. One that caught my eye was kUTTypeToDoItem:

import MobileCoreServices

kUTTypeToDoItem // "public.to-do-item"

As a public type, iOS & OS X now provide a unified way to share tasks between applications. If you happen to work on a task management tool (and, let’s be honest, the chances are extremely good, considering how damn many of them there are in the App Store), proper integration with this system type should be put at the top of your list.

kCGImageMetadataShouldExcludeGPS

Most users are completely unaware that most pictures taken with phones these days include GPS metadata. Countless individuals have had their privacy breached because of this small detail.

New to the Image I/O framework is a convenient new option for CGImageDestination: kCGImageMetadataShouldExcludeGPS, which does what you’d expect.

import UIKit
import ImageIO
import MobileCoreServices

let image = ...
let fileURL = NSURL(fileURLWithPath: "/path/to/output.jpg")
let options: NSDictionary = [kCGImageDestinationLossyCompressionQuality as NSString: 0.75,
                            kCGImageMetadataShouldExcludeGPS as NSString: true]

if let imageDestination = CGImageDestinationCreateWithURL(fileURL, kUTTypeJPEG, 1, nil),
    let cgImage = image.CGImage
{
    CGImageDestinationAddImage(imageDestination, cgImage, options)
    CGImageDestinationFinalize(imageDestination)
}

WTF_PLATFORM_IOS

#define WTF_PLATFORM_IOS has been removed from JavaScriptCore. It will be missed.

WKWebView

UIWebView is dead. Long live WKWebView.

WKWebView offers Safari-level performance to your own app, and further improves on UIWebView with preferences and configurations:

import WebKit

let preferences = WKPreferences()
preferences.javaScriptCanOpenWindowsAutomatically = false

let configuration = WKWebViewConfiguration()
configuration.preferences = preferences

let webView = WKWebView(frame: self.view.bounds, configuration: configuration)
let request = NSURLRequest(URL: NSURL(string: "https://nshipster.com")!)
webView.loadRequest(request)

NSQualityOfService

Threads have been systematically de-emphasized from the conceptual foundation of Apple frameworks. This has been a good thing for developers.

Following this trend is a change to NSOperation in the latest APIs. A new qualityOfService property replaces the threadPriority. These new semantics allow an app to defer non-critical work to ensure a consistently great user experience.

The NSQualityOfService enum defines the following values:

  • UserInteractive: UserInteractive QoS is used when performing work that is related to graphically intensive work such as scrolling or animating.
  • UserInitiated: UserInitiated QoS is used for performing work that has been explicitly requested by the user, but does not require millisecond accuracy like animations. For example, if a user requests an email app to check for mail right now.
  • Utility: Utility QoS is used for performing work that has been requested by the user to happen automatically. For example, an email app may be configured to automatically check for mail every 5 minutes. It is not a problem if the email check is deferred by a few minutes if the system is extremely limited in resources.
  • Background: Background QoS is used for performing work that the user may not even be aware is happening on their behalf. For example, an email app may use this to perform indexing for a search.

Quality of Service is used throughout Foundation in iOS 8 & OS X Yosemite, so be on the lookout for opportunities to capitalize on this new feature.

LocalAuthentication

Finally, one of the most anticipated features of iOS 8: LocalAuthentication. Ever since TouchID was introduced with the iPhone 5s, developers have been salivating at the prospect of using that in their own app.

Imagine: with CloudKit and LocalAuthentication, nearly all of the friction to creating a user account is gone. Just scan your fingerprint, and you’re in.

LocalAuthentication works in terms of an LAContext class, which evaluates a specified policy, and gives a thumbs up or thumbs down on user authentication. At no point is any biometric information made available to the application—everything is kept safe on the hardware itself.

let context = LAContext()
var error: NSError?

if context.canEvaluatePolicy(.DeviceOwnerAuthenticationWithBiometrics, error: &error) {
    context.evaluatePolicy(.DeviceOwnerAuthenticationWithBiometrics, localizedReason: "...") { success, error in
        if success {
            
        } else {
            print("Error: \(error)")
        }
    }
} else {
    print("Error: \(error)")
}

Although it seems like all that anyone can talk about these days is Swift, it’d be a shame if we ignored all of the neat things iOS 8 & OS X Yosemite allow us to actually do with this new language.

If you’re feeling adventurous, dive into the iOS 7.1 to 8.0 API diffs to really appreciate the magnitude of new technologies to discover. Granted, of the 4000+ new APIs, at least half of those are slight changes to Accelerate functions, or methods becoming properties, but still… Have at it!

NSMutableHipster

Questions? Corrections? Issues and pull requests are always welcome.

This article uses Swift version 2.0 and was last reviewed on Sep 12, 2015. Find status information for all articles on the status page.

Written by Mattt
Mattt

Mattt (@mattt) is a writer and developer in Portland, Oregon.

Next Article

What if, instead of lowering source code down for the purpose of execution, we raised it for the purpose of understanding?