# iOS 7

By [Mattt](https://nshipster.com/authors/mattt).
Published 2013-09-23.
Canonical URL: <https://nshipster.com/ios7>

With the NDA finally lifted, we can finally talk about all of the amazing new APIs in iOS 7. And there are a _lot_ of them. “1500 new APIs”, by Apple’s count during the WWDC Keynote. (Granted, a good portion of that could just be all of the changes from `id` to `instancetype`, but that’s a huge number, regardless).

We’ll be going over many of the new features of iOS 7 in depth over the coming weeks, but with all of the excitement around this major release, this week’s issue will hit on some of the gems hiding in plain sight: `NSData` Base64 encoding, `NSURLComponents`, `NSProgress`, `CIDetectorSmile`, `CIDetectorEyeBlink`, `SSReadingList`, `AVCaptureMetadataOutput`, `AVSpeechSynthesizer`, and `MKDistanceFormatter`.

---

## NSData (NSDataBase64Encoding)

[Base64](https://en.wikipedia.org/wiki/Base64) is a general term for encoding binary data as ASCII text. This is used all over the place on the web, since many core technologies are designed to support text, but not raw binary. For instance, CSS can embed images with [inline `data:` URIs](https://en.wikipedia.org/wiki/Data_URI_scheme), which are often Base64-encoded. Another example is [Basic `Authorization` headers](https://en.wikipedia.org/wiki/Basic_access_authentication), which Base64-encodes its username/password pair, which is marginally better than having them completely in the clear.

For the longest time, this boringly essential function was completely MIA, leaving thousands of developers to copy-paste random code snippets from forum threads. It was an omission as conspicuous and annoying as JSON pre-iOS 5.

But no longer! iOS 7 finally bakes-in Base64:

```swift
let string = "Lorem ipsum dolor sit amet."
if let data = string.dataUsingEncoding(NSUTF8StringEncoding) {
    let base64EncodedString = data.base64EncodedStringWithOptions([])

    print(base64EncodedString)    // TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQu
}
```

```objc
NSString *string = @"Lorem ipsum dolor sit amet.";
NSString *base64EncodedString = [[string dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0];

NSLog(@"%@", base64EncodedString);  // TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQu
```

## NSURLComponents & NSCharacterSet (NSURLUtilities)

Foundation is blessed with a wealth of functionality for working with URIs. Unfortunately, many of the APIs for manipulating URLs are strewn across `NSString`, since `NSURL` is immutable.

`NSURLComponents` dramatically improves this situation. Think of it as `NSMutableURL`:

```swift
if let components = NSURLComponents(string: "https://nshipster.com") {
    components.path = "/iOS7"
    components.query = "foo=bar"
    
    print(components.scheme!)     // https
    print(components.URL!)        // https://nshipster.com/iOS7?foo=bar
}
```

```objc
NSURLComponents *components = [NSURLComponents componentsWithString:@"https://nshipster.com"];
components.path = @"/iOS7";
components.query = @"foo=bar";

NSLog(@"%@", components.scheme);    // https
NSLog(@"%@", [components URL]);     // https://nshipster.com/iOS7?foo=bar
```

Each property for URL components also has a `percentEncoded*` variation (e.g. `user` & `percentEncodedUser`), which forgoes any additional URI percent encoding of special characters.

Which characters are special, you ask? Well, it depends on what part of the URL you’re talking about. Good thing that `NSCharacterSet` adds a new category for allowed URL characters in iOS 7:

- `+ (id)URLUserAllowedCharacterSet`
- `+ (id)URLPasswordAllowedCharacterSet`
- `+ (id)URLHostAllowedCharacterSet`
- `+ (id)URLPathAllowedCharacterSet`
- `+ (id)URLQueryAllowedCharacterSet`
- `+ (id)URLFragmentAllowedCharacterSet`

## NSProgress

`NSProgress` is a tough class to describe. It acts as both an observer and a delegate / coordinator, acting as a handle for reporting and monitoring progress. It integrates with system-level processes on OS X, but can also be plugged into user-facing UI. It can specify handlers for pausing and canceling, which then forward onto the operation actually doing the work.

Anything with a notion of completed and total units is a candidate for `NSProgress`, whether it’s the bytes written to a file, the number of frames in a large render job, or the files downloaded from a server.

`NSProgress` can be used to simply report overall progress in a localized way:

```swift
let progress = NSProgress(totalUnitCount: 100)
progress.completedUnitCount = 42;

print(progress.localizedDescription) // 42% completed
```

```objc
NSProgress *progress = [NSProgress progressWithTotalUnitCount:100];
progress.completedUnitCount = 42;

NSLog(@"%@", [progress localizedDescription]); // 42% completed
```

…or it can be given a handler for stopping work entirely:

```swift
let timer = NSTimer(timeInterval: 1.0, target: self, selector: "incrementCompletedUnitCount:",
    userInfo: nil, repeats: true)

progress.cancellationHandler = {
    timer.invalidate()
}

progress.cancel()
```

```objc
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0
                                         target:self
                                       selector:@selector(incrementCompletedUnitCount:) userInfo:nil
                                        repeats:YES];

progress.cancellationHandler = ^{
    [timer invalidate];
};

[progress cancel];
```

`NSProgress` makes a lot more sense in the context of OS X Mavericks, but for now, it remains a useful class for encapsulating the shared patterns of work units.

## NSArray -firstObject

Rejoice! The `NSRangeException`-dodging convenience of `-lastObject` has finally been extended to the first member of an `NSArray`. (Well, it _has_ been there as a private API since \~iOS 4, but that’s water under the bridge now).

Behold!

```swift
let array = [1, 2, 3] as NSArray

print("First Object: \(array.firstObject)")   // First Object: Optional(1)
print("Last Object: \(array.lastObject)")     // Last Object: Optional(3)
```

```objc
NSArray *array = @[@1, @2, @3];

NSLog(@"First Object: %@", [array firstObject]); // First Object: 1
NSLog(@"Last Object: %@", [array lastObject]);   // Last Object: 3
```

Refreshing!

## CIDetectorSmile & CIDetectorEyeBlink

> As a random aside, shouldn’t it be a cause for concern that the device most capable of taking embarrassing photos of ourselves is also the device most capable of distributing it to millions of people? Just a thought.

Since iOS 5, the Core Image framework has provided facial detection and recognition functionality through the `CIDetector` class. If it wasn’t insaneballs enough that we could detect faces in photos, in iOS 7 we can even tell if that face is smiling or has its eyes closed. \*shudder\*

In yet another free app idea, here’s a snippet that might be used by a camera that only saves pictures of smiling faces:

```swift
import CoreImage
        
let smileDetector = CIDetector(ofType: CIDetectorTypeFace, context: context,
    options: [CIDetectorTracking: true, CIDetectorAccuracy: CIDetectorAccuracyLow])

var features = smileDetector.featuresInImage(ciImage, options: [CIDetectorSmile: true])

if let feature = features.first as? CIFaceFeature where feature.hasSmile {
    UIImageWriteToSavedPhotosAlbum(UIImage(CIImage: ciImage), self, "didFinishWritingImage", &features)
} else {
    label.text = "Say Cheese!"
}
```

```objc
@import CoreImage;

CIDetector *smileDetector = [CIDetector detectorOfType:CIDetectorTypeFace
                                context:context
                                options:@{CIDetectorTracking: @YES,
                                          CIDetectorAccuracy: CIDetectorAccuracyLow}];
NSArray *features = [smileDetector featuresInImage:ciImage options:@{CIDetectorSmile:@YES}];
if (([features count] > 0) && (((CIFaceFeature *)features[0]).hasSmile)) {
    UIImageWriteToSavedPhotosAlbum([UIImage imageWithCIImage:ciImage], self, @selector(didFinishWritingImage), features);
} else {
    self.label.text = @"Say Cheese!";
}
```

## AVCaptureMetadataOutput

Scan UPCs, QR codes, and barcodes of all varieties with `AVCaptureMetadataOutput`, new to iOS 7. All you need to do is set it up as the output of an `AVCaptureSession`, and implement the `captureOutput:didOutputMetadataObjects:fromConnection:` method accordingly:

```swift
import AVFoundation

let session = AVCaptureSession()
let device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
var error: NSError?

do {
    let input = try AVCaptureDeviceInput(device: device)
    session.addInput(input)
} catch let error {
    print("Error: \(error)")
}

let output = AVCaptureMetadataOutput()
output.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
session.addOutput(output)
output.metadataObjectTypes = [AVMetadataObjectTypeQRCode]

session.startRunning()

// MARK: - AVCaptureMetadataOutputObjectsDelegate

func captureOutput(
    captureOutput: AVCaptureOutput!,
    didOutputMetadataObjects metadataObjects: [AnyObject]!,
    fromConnection connection: AVCaptureConnection!) {
        
        var QRCode: String?
        for metadata in metadataObjects as! [AVMetadataObject] {
            if metadata.type == AVMetadataObjectTypeQRCode {
                // This will never happen; nobody has ever scanned a QR code... ever
                QRCode = (metadata as! AVMetadataMachineReadableCodeObject).stringValue
                break
            }
        }
        
        print("QRCode: \(QRCode)")
}
```

```objc
@import AVFoundation;

AVCaptureSession *session = [[AVCaptureSession alloc] init];
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;

AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device
                                                                    error:&error];
if (input) {
    [session addInput:input];
} else {
    NSLog(@"Error: %@", error);
}

AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc] init];
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
[session addOutput:output];
[output setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];

[session startRunning];

#pragma mark - AVCaptureMetadataOutputObjectsDelegate

- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputMetadataObjects:(NSArray *)metadataObjects
       fromConnection:(AVCaptureConnection *)connection
{
    NSString *QRCode = nil;
    for (AVMetadataObject *metadata in metadataObjects) {
        if ([metadata.type isEqualToString:AVMetadataObjectTypeQRCode]) {
            // This will never happen; nobody has ever scanned a QR code... ever
            QRCode = [(AVMetadataMachineReadableCodeObject *)metadata stringValue];
            break;
        }
    }

    NSLog(@"QR Code: %@", QRCode);
}
```

`AVFoundation` supports every code you’ve heard of (and probably a few that you haven’t):

- `AVMetadataObjectTypeUPCECode`
- `AVMetadataObjectTypeCode39Code`
- `AVMetadataObjectTypeCode39Mod43Code`
- `AVMetadataObjectTypeEAN13Code`
- `AVMetadataObjectTypeEAN8Code`
- `AVMetadataObjectTypeCode93Code`
- `AVMetadataObjectTypeCode128Code`
- `AVMetadataObjectTypePDF417Code`
- `AVMetadataObjectTypeQRCode`
- `AVMetadataObjectTypeAztecCode`

If nothing else, `AVCaptureMetadataOutput` makes it possible to easily create a Passbook pass reader for the iPhone and iPad. There’s still a lot of unrealized potential in Passbook, so here’s to hoping that this API will be a factor in more widespread adoption.

## SSReadingList

Even though the number of people who have actually read something saved for later is only marginally greater than [the number of people who have ever used a QR code](http://picturesofpeoplescanningqrcodes.tumblr.com/), it’s nice that iOS 7 adds a way to add items to the Safari reading list with the new Safari Services framework.

```swift
import SafariServices

let url = NSURL(string: "https://nshipster.com/ios7")!
try? SSReadingList.defaultReadingList()?.addReadingListItemWithURL(url, title: "NSHipster", previewText: "...")
```

```objc
@import SafariServices;

NSURL *URL = [NSURL URLWithString:@"https://nshipster.com/ios7"];
[[SSReadingList defaultReadingList] addReadingListItemWithURL:URL
                                                        title:@"NSHipster"
                                                  previewText:@"..."
                                                        error:nil];
```

## AVSpeechSynthesizer

Text-to-Speech has been the killer feature of computers for [accessibility](https://nshipster.com/uiaccessibility/) and [pranking](https://nshipster.com/icloud/) enthusiasts since its inception in the late 1960s.

iOS 7 brings the power of Siri with the convenience of a [Speak & Spell](https://en.wikipedia.org/wiki/Speak_%26_Spell_%28toy%29) in a new class `AVSpeechSynthesizer`:

```swift
import AVFoundation

let synthesizer = AVSpeechSynthesizer()
let utterance = AVSpeechUtterance(string: "Just what do you think you're doing, Dave?")
utterance.rate = AVSpeechUtteranceMinimumSpeechRate   // Tell it to me slowly
synthesizer.speakUtterance(utterance)
```

```objc
@import AVFoundation;

AVSpeechSynthesizer *synthesizer = [[AVSpeechSynthesizer alloc] init];
AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:@"Just what do you think you're doing, Dave?"];
utterance.rate = AVSpeechUtteranceMinimumSpeechRate;  // Tell it to me slowly
[synthesizer speakUtterance:utterance];
```

## MKDistanceFormatter

Finally, we end our showcase of iOS 7’s new and noteworthy APIs with another class that has NSHipsters crying out “finally!”: `MKDistanceFormatter`.

As advertised, `MKDistanceFormatter` provides a way to convert distances into localized strings using either imperial or metric units:

```swift
import CoreLocation
import MapKit

let sanFrancisco = CLLocation(latitude: 37.775, longitude: -122.4183333)
let portland = CLLocation(latitude: 45.5236111, longitude: -122.675)
let distance = portland.distanceFromLocation(sanFrancisco)

let formatter = MKDistanceFormatter()
formatter.units = .Imperial
print(formatter.stringFromDistance(distance)) // 535 miles
```

```objc
@import CoreLocation;
@import MapKit;

CLLocation *sanFrancisco = [[CLLocation alloc] initWithLatitude:37.775 longitude:-122.4183333];
CLLocation *portland = [[CLLocation alloc] initWithLatitude:45.5236111 longitude:-122.675];
CLLocationDistance distance = [portland distanceFromLocation:sanFrancisco];

MKDistanceFormatter *formatter = [[MKDistanceFormatter alloc] init];
formatter.units = MKDistanceFormatterUnitsImperial;
NSLog(@"%@", [formatter stringFromDistance:distance]); // 535 miles
```

---

So there you have it! This was just a small sample of the great new features of iOS 7. Still craving more? Check out Apple’s [“What’s New in iOS 7”](https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS7.html) guide on the Developer Center.
