iOS 7

译自 NSHispter

NDA 终于结束了,我们终于可以聊聊 iOS 7 里超赞的新 API 了。而且新 API 有非常多。苹果在 WWDC Keynote 中说有“1500 个新 API”,。(尽管里面很大一部分只是把 id 换成了 instancetype,但是不管怎么说还是非常多)

带着版本升级的激动心情,接下来的几周我们会深入讨论 iOS 7 的许多新特性,但是这周,我们来看下那些隐藏颇深的新特性:NSData Base64 编码, NSURLComponents, NSProgress, NSProcessInfo activities, CIDetectorSmile, CIDetectorEyeBlink, SSReadingList, AVCaptureMetaDataOutput, AVSpeechSynthesizerMKDistanceFormatter


NSData (NSDataBase64Encoding)

Base64 是一种常用的把二进制编码成 ASCII 字符的手段。由于许多常用 web 技术只支持文本,并不支持二进制,所以 Base64 应用十分广泛。例如,CSS 可以用 inline data:// URIs 来内嵌图片,这种形式就经常使用 Base64 编码的文本。另一个例子是 Basic Authentication headers,它使用 Base64 编码用户名/密码对,稍微比完全明文好一点吧。

长久以来,这个重要的函数压根就没有,数以千计的开发者治好到论坛上随便抓一段代码贴过来就用。如此显而易见的缺失就像 iOS 5 之前的 JSON 一样恼人。

但是现在!iOS 7 终于内置了 Base64:

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

NSLog(@"%@", base64EncodedString); // @"TG9yZW0gaXBzdW0gZG9sYXIgc2l0IGFtZXQu"

NSURLComponents & NSCharacterSet (NSURLUtilities)

Foundation 框架里面有许多 URI 相关的功能。不过,很多功能是用 NSString 实现的,因为 NSURL 是不可改变的。

NSURLComponents 极大地改善了这种情况。把它当成是 NSMutableURL 就好了:

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

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

URL components 的每个属性都有一个 percentEncoded* 的变种(例如 userpercentEncodedUser),这些变种不会对 URI 中的特殊字符进行百分号编码。

你说那些字符算是特殊字符?好吧,这跟字符处在 URL 的哪个部分有关。好消息是 iOS 7 中 NSCharacterSet 增加了一个新的 category 来查询允许出现在 URL 中的字符。

  • + (id)URLUserAllowedCharacterSet
  • + (id)URLPasswordAllowedCharacterSet
  • + (id)URLHostAllowedCharacterSet
  • + (id)URLPathAllowedCharacterSet
  • + (id)URLQueryAllowedCharacterSet
  • + (id)URLFragmentAllowedCharacterSet

NSProgress

NSProgess 是一个有点难描述的类。它既是一个观察者,又是一个委托/协调者,扮演者报告和监控过程的角色。它在 OS X 上的系统级进程进行了整合,不过也可以用于面向用户的 UI。它可以指定暂停和取消的处理逻辑,让这些代码来真正地暂停和取消操作。

任何具有完成状态和总量概念的东西都可以使用 NSProgress,不管是写入文件的字节数、渲染的帧数,还是要从服务器上下载的文件数。

NSProgress 可以用本地化的方式来汇报总体进度:

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

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

…或者可以给一个代码块来让操作完全停止:

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

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

[progress cancel];

NSProgess 在 Mac OS X 10.9 Mavericks 下很有用,但是在当前也还是一个包装工作量这个模式的一个很好的类。

NSArray -firstObject

欢呼吧!NSArray 中避免 NSRangeException-lastObject 终于有了对应的取第一个元素对应方法。(好吧,实际上这个方法在 iOS 4 的时候就已经成为私有 API 了,但是现在终于开放出来了)

注意了!

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

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

爽!

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 or people? Just a thought.

iOS 5 以后,Core Image 在 CIDetector 类中加入了面部识别和检测功能。如果检测照片中的人脸还不够炫的话,在 iOS 7 中我们甚至能检测笑脸和闭眼。*发抖*

又一个免费 app 的点子,只保存笑脸照片的相机 app,其中的一段代码可能是这样的:

@import CoreImage;
CIDetector *smileDetector = [CIDetector detectorOfType:CIDetectorSmile 
                                context:context 
                                options:@{CIDetectorTracking: @YES, 
                                          CIDetectorAccuracy: CIDetectorAccuracyLow}];
NSArray *features = [smileDetector featuresInImage:image];
if ([features count] > 0) {
    UIImageWriteToSavedPhotosAlbum(image, self, @selector(didFinishWritingImage), features);
} else {
    self.label.text = @"Say Cheese!"
}

AVCaptureMetaDataOutput

iOS 7 里在 AVCaptureMetaDataOutput 中新增加了扫描 UPC、QR 码、普通条形码和其他变种的方法。只需要添加一个 AVCaptureSession 的输出,实现相应的 captureOutput:didOutputMetadataObjects:fromConnection: 方法:

@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];

[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 支持你听说过的所有条形码(大概还有一些没听过的):

  • AVMetadataObjectTypeUPCECode
  • AVMetadataObjectTypeCode39Code
  • AVMetadataObjectTypeCode39Mod43Code
  • AVMetadataObjectTypeEAN13Code
  • AVMetadataObjectTypeEAN8Code
  • AVMetadataObjectTypeCode93Code
  • AVMetadataObjectTypeCode128Code
  • AVMetadataObjectTypePDF417Code
  • AVMetadataObjectTypeQRCode
  • AVMetadataObjectTypeAztecCode

就算没有其他用途,AVCaptureMetaDataOutput 也使得在 iPhone 和 iPad 上创建一个 Passbook 条码读取器变得非常简单。

SSReadingList

即使真正会看保存在稍后阅读中的文章的人很少,可能只比扫描过 QR 码的人多一些。iOS 7 里新的 Safari Services framework 还是带来一个不错的功能:向 Safari 的阅读列表中添加文章。

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

AVSpeechSynthesizer

语音合成(TTS)技术在 1960 年代后期出现以来,就成为了可用性恶作剧爱好者的利器。

iOS 7 把 Siri 的方便易用的Speak & Spell 功能带到了一个新的类 AVSpeechSynthesizer 中:

AVSpeechSynthesizer *synthesizer = [[AVSpeechSynthesizer alloc] init];
AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:@"Hello, World!"];
utterance.rate = AVSpeechUtteranceMinimumSpeechRate; // Tell it to me slowly
[synthesizer speakUtterance:utterance];

MKDistanceFormatter

最后,我们用 NSHipster 会喊出“终于来了!”的另一个类来结束我们的 iOS 7 的又新又炫的新 API 介绍,它就是:MKDistanceFormatter

就像上面说的,MKDistanceFormatter 提供了一种把公制/英制距离转换成本地化字符串的方法。

@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

就是这些了!这只是 iOS 7 超棒的新特性中很小的一部分。想要更多?看下 Apple Developer Center 中的文档 "What's New in iOS 7"

Comments