iOS 8
Written by Mattt Thompson on
Ask anyone, and they'll tell you: WWDC 2014 was the 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 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 toNSNumberFormatter
,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
println(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
println(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
println(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(), withHandler: { data, error in
if !error {
println("Steps Taken: \(data.numberOfSteps)")
var distance = data.distance.doubleValue
println("Distance: \(lengthFormatter.stringFromMeters(distance))")
var time = data.endDate.timeIntervalSinceDate(data.startDate)
var speed = distance / time
println("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(), withHandler: { data, error in
if !error {
println("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: AnyObject[]!) {
var location: CLLocation? = locations[0] as? CLLocation
var floor: CLFloor? = location!.floor
println("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())
for item: AnyObject in statistics!.sources {
if let source = item as? HKSource {
if let quantity: HKQuantity = statistics!.sumQuantityForSource(source) {
if quantity.isCompatibleWithUnit(HKUnit.gramUnitWithMetricPrefix(.Kilo)) {
let massFormatter = NSMassFormatter()
let kilograms = quantity.doubleValueForUnit(HKUnit.gramUnitWithMetricPrefix(.Kilo))
println(massFormatter.stringFromKilograms(kilograms))
}
if quantity.isCompatibleWithUnit(HKUnit.meterUnit()) {
let lengthFormatter = NSLengthFormatter()
let meters = quantity.doubleValueForUnit(HKUnit.meterUnit())
println(lengthFormatter.stringFromMeters(meters))
}
if quantity.isCompatibleWithUnit(HKUnit.jouleUnit()) {
let energyFormatter = NSEnergyFormatter()
let joules = quantity.doubleValueForUnit(HKUnit.jouleUnit())
println(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(hostname: "nshipster.com",
port: 5432,
inputStream: &inputStream,
outputStream: &outputStream)
NSString -localizedCaseInsensitiveContainsString
Also filed under: "small but solid fixes", is this
convenience method for NSString
:
let string: NSString = "Café"
let substring: NSString = "É"
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:
猫
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: "http://nshipster.com"), completionHandler: { data, response, error in
// ...
})
let protectionSpace = NSURLProtectionSpace()
NSURLCredentialStorage.getCredentialsForProtectionSpace(protectionSpace: protectionSpace, task: task, completionHandler: { 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;
UIImage *image = ...;
NSURL *fileURL = [NSURL fileURLWithPath:@"/path/to/output.jpg"];
NSString *UTI = kUTTypeJPEG;
NSDictionary *options = @{
(__bridge id)kCGImageDestinationLossyCompressionQuality: @(0.75),
(__bridge id)kCGImageMetadataShouldExcludeGPS: @(YES),
};
CGImageDestinationRef imageDestinationRef =
CGImageDestinationCreateWithURL((__bridge CFURLRef)fileURL,
(__bridge CFStringRef)UTI,
1,
NULL);
CGImageDestinationAddImage(imageDestinationRef, [image CGImage], (__bridge CFDictionaryRef)options);
CGImageDestinationFinalize(imageDestinationRef);
CFRelease(imageDestinationRef);
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: "http://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.
LAContext *context = [[LAContext alloc] init];
NSError *error = nil;
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
error:&error])
{
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:NSLocalizedString(@"...", nil)
reply:^(BOOL success, NSError *error) {
if (success) {
// ...
} else {
NSLog(@"%@", error);
}
}];
} else {
NSLog(@"%@", 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!