iOSDC「あなたの知らない連絡先の世界」「詳解Storyboard」補足&参加した感想


iOSDC Japan 2020で発表してきた内容でいくつか受けた質問の答えや補足&参加した感想になります

## あなたの知らない連絡先の世界

はてなブックマークで紹介されたらしく、えらい数見ていただいてます。

https://speakerdeck.com/coe/anatafalsezhi-ranailian-luo-xian-falseshi-jie

### vCard 3.0（iOS）と vCard 2.1（Android）
Androidで取得したvCardは、Quoted-printableがかかっています。

iOS

```
BEGIN:VCARD
VERSION:3.0
PRODID:-//Apple Inc.//iPhone OS 13.6//EN
N:日向;強;搩;ドクター;三世
FN:ドクター 強 搩 日向 三世
END:VCARD
```

Android 

```
BEGIN:VCARD
    VERSION:2.1
    N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E6=97=A5=E5=90=91=E7=81=98;=E5=BC=B7;=46;=44=72;=4A=72
    FN;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=44=72=20=E6=97=A5=E5=90=91=E7=81=98=20=46=20=E5=BC=B7=20=4A=72
    END:VCARD
```


### PersonNameComponentsFormatterはサーバーサイドSwiftで使える？

使えませんでした。NSUnsupportedでした。
https://github.com/apple/swift-corelibs-foundation/blob/2a5bc4d8a0b073532e60410682f5eb8f00144870/Sources/Foundation/PersonNameComponentsFormatter.swift

### Objective-Cでしか使えないメソッド
本編で紹介した `enumeratorForChangeHistoryFetchRequest:error:` の他にも、
連絡先を取得する`enumeratorForContactFetchRequest:error:` もObjective-Cでしか使えません。
SwiftUI? Combine? 甘
https://developer.apple.com/documentation/contacts/cncontactstore/3294191-enumeratorforcontactfetchrequest?changes=_3&language=objc


## 詳解storyboard

https://speakerdeck.com/coe/xiang-jie-storyboard

### SceneDelegateのリストア
今回発表されていた `iPadOSDC: Multiple Windows` で解説されています。
https://speakerdeck.com/hironytic/ipadosdc-multiple-windows

Appleのサンプルドキュメントもあります。
https://developer.apple.com/documentation/uikit/uiscenedelegate/restoring_your_app_s_state

### Storyboardの分割
WWDC 19の`Great Developer Habits`にてStoryboardの分割に触れています。
その他にもエンジニアにとって重要な話をしているので、このセッションは非常におすすめです。
https://developer.apple.com/videos/play/wwdc2019/239/

### まばたきの検出
本編では省略したまばたきの検出コードですが、例えば `AVCaptureVideoDataOutputSampleBufferDelegate` でCMSampleBufferから顔を検出し、目を閉じている、開いているを検出して判定するとまばたきの検出が行えます。
以下のコードは、両目を閉じている人を検出してアクションを起こす例です。まばたき検出はもうちょっとコードが必要になります。

```swift
    // MARK: - AVCaptureVideoDataOutputSampleBufferDelegate
    func captureOutput(_ output: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) {
        let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
        let ciImage: CIImage = CIImage(cvPixelBuffer: pixelBuffer)
        
        guard let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: [CIDetectorAccuracy:CIDetectorAccuracyHigh]) else {
            return
        }
        
        let faceFeatures = faceDetector.features(in: ciImage, options: [
            CIDetectorSmile:true,
            CIDetectorEyeBlink:true
        ]) as! [CIFaceFeature]
        
        let closedEyes = faceFeatures.filter { (faceFeature) -> Bool in
            return faceFeature.leftEyeClosed && faceFeature.rightEyeClosed
        }
        if !closedEyes.isEmpty {
            self.sendActions(for: .primaryActionTriggered)
        }
    }
```

# 個人的な感想
よかったら二つやりませんか？との打診を受けて、Storyboardだけやろうかなと思ったんですが、せっかくだから俺はどちらとも選ぶぜという勢いのみでやってみました。
結果的には本命のStoryboardではなく連絡先が好評？だったので、フェイルセーフが働いて良かったです。
セッションは20分コースと40分コースがあって、40分喋り続けるのきついなと思ってチキって20分にしたんですが、「持ち時間が40分もらえる」という考えであれば、次出られるのであれば長い時間はありかな、と思いました。Storyboardは説明がいっぱいいっぱいだったので小粋なトークが挟めませんでした。
なんにせよ、見ていただいた方、ご清聴ありがとうございました！ご清聴？