iOS15で便利になった通知機能の紹介 フォーカス、通知サマリ、コミュニケーション通知など

こんにちは。[virapture](https://virapture.com)株式会社の[もぐめっと](https://mogmet.com)です。

![IMG_2894.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42234/6e1d16c4-db98-6c62-8e60-2d673c0a1a0f.jpeg)
最近Apex Legendsというゲームにハマっているのですが、強い人にきゃりーしてもらわないと中々チャンピオンがとれずに苦戦しております。

今回は[集まれSwift好き！Swift愛好会スピンオフ WWDC21セッション要約会 @オンライン](https://love-swift.connpass.com/event/215687/)というイベントでWWDC2021の[Send communication and Time Sensitive notifications
](https://developer.apple.com/videos/play/wwdc2021/10091/)について発表した件についてになります。

https://www.slideshare.net/mogmet/ios-15-notification-focusnotification-summarysend-communication/

こちらの発表ですが、かなり巻で喋った（という想定で記事を書いています）のでじっくりとメモをとれなかった方々のために記事バージョンも用意しました。
ご査収ください。

## アクションとアイコンが関連付けできる

iOS15からアクションの横に視覚的なコンテキストとしてアイコンを表示できるようになりました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42234/25107ac6-7592-5afb-c1d9-853106575911.png" width=300 />

実装はUNNotificationActionIconオブジェクトを作成して、UNNofiticationActionに渡すだけです。

```NotificationActionIcon.swift
// Setting up notification actions with icons

let likeActionIcon = UNNotificationActionIcon(systemImageName: "hand.thumbsup")
let likeAction = UNNotificationAction(identifier: "like-action",
                                           title: "Like",
                                         options: [],
                                            icon: likeActionIcon)
        
let commentActionIcon = UNNotificationActionIcon(templateImageName: "text.bubble")
let commentAction = UNTextInputNotificationAction(identifier: "comment-action",
                                                       title: "Comment",
                                                     options: [],
                                                        icon: commentActionIcon,
                                        textInputButtonTitle: "Post",
                                        textInputPlaceholder: "Type here…")

let category = UNNotificationCategory(identifier: "update-actions",
                                         actions: [likeAction, commentAction],
                               intentIdentifiers: [], options: [])
```

category使ってその後どうするかは下記記事に移譲します。

https://dev.classmethod.jp/articles/user-notifications-framework-10

## 通知がスケジュールされた時間にサマリーとして配信されるようになった
通知サマリーの導入により、通知によるアクティブな割り込みを減らし、あらかじめ設定された時間にまとめて通知を提示することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42234/1f79889f-4b46-a701-5c5b-122276e8d343.png)

スケジュールに沿ってまとめて配信されたすべての通知は、ロック画面の通知リストに表示され、通知コンテンツにメディアの添付ファイルを含めることで、その通知が通知サマリーの一番上に表示される可能性が高くなります。

また、新しく実装された関連性スコアを設定すると、通知サマリに出やすくなります。

```swift
content.relevanceScore = 0.5
```
CF: [Instance Property relevanceScore](https://developer.apple.com/documentation/usernotifications/unnotificationcontent/3821031-relevancescore)

## フォーカスを設定して通知の表示や中断をフィルタリングできるようになった

フォーカス設定では、割り込み通知を送信できる人やアプリケーションを選択できます。
例えば、「仕事」のフォーカスでは、「メール」と「メッセージ」が通知の送信を許可され、同僚からの直接の連絡も許可されます。
つまり、通知のサマリとフォーカスによって、どのアプリケーションがいつ通知を送信できるかが制御されます。
これにより、1日の中での通知による割り込みをより適切に管理することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42234/92b24c83-0e6d-ba5c-4a5e-d266be4276d8.png)

こちらの実装などに関しては後ほどコミュニケーション通知の部分について詳細をお話します。

## 割込レベルが4つできました

どの程度通知を行うかのレベルを設定するものとして割り込みレベルというものが4つできました。

割り込みレベルは下記の4つに分けられます

- Passive (←New!)
- Active
- Time Sensitive (←New!)
- Critical

それぞれ解説していきます。

### １． Passiveは静かに通知

Passive割込レベルの通知は、警告や画面の点灯を行わず、システム制御を突破することもありません。
これらの通知は、通知リストに静かに配信され、次にリストが表示されたときに利用可能になります。
すぐに注意を払う必要はないが、いずれ見られるはずの通知に使用する必要があります。
例としては、おすすめの食事や新しいエピソードの配信などがあります。

Netflixの新着エピソードお知らせとか、LIPSのおすすめコスメの紹介とかに使えそうですね。

### 2. Activeは今までのデフォルト

通知が音や振動を流したり、画面を光らせたりする、現在存在する動作に似ています。
これらの通知は、通知のシステム管理を突破することはありません。
これがデフォルトの割込レベルです。
システムが現在割込しないように設定されている場合、割込する必要のない通知に使用する必要があります。
例えば、スポーツ速報やライブストリームビデオの通知などです。

スマートニュースの速報や、abema TVの予約してたライブの通知とかよくある通知に使うイメージですね。

### 3. Time Sensitiveはすぐに注意を払う必要がある通知

Time Sensitive通知は、Active通知と同じように警告します。
許可されていれば、通知サマリーやフォーカスなどのシステムコントロールを突破して、すぐに目立つように通知が掲載されるので特別な通知になります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42234/0eb7743d-463c-eda4-3fe3-a86e58c6f25c.png" width=300 />
この通知は、ユーザがすぐに注意を払う必要があり、積極的に割込することが適切な場合にのみ使用します。
例えば、アカウントのセキュリティや荷物の配送に関する警告、薬の服用に関するリマインダーの通知などです。



タイムセンシティブ通知を送信する際には、信頼関係を維持することが重要です。
割り込みの性質を利用しすぎず、適切な場合にのみ利用しましょう！

もぐめっとはシュタインズゲートというゲームが好きなのですが、そこで途中で出てくる「[お前を見ているぞ](https://dic.nicovideo.jp/a/%E3%81%8A%E5%89%8D%E3%82%92%E8%A6%8B%E3%81%A6%E3%81%84%E3%82%8B%E3%81%9E)」という部分で主人公に対して通知するところを彷彿しました。
立入禁止区域に踏み込もうとしているユーザに対して通知するとかいいかもしれませんね。

### 4. Criticalは既存のCritical通知と一緒

iOS 14に存在するCritical通知のサポートに相当します。
この割込レベルの通知は、積極的に警告を発し、システム制御を突破し、さらにデバイスの呼出音スイッチをバイパスします。
このような破壊的な動作のため、クリティカルな割込には承認された資格が必要です。
クリティカルな通知のユースケースとしては、悪天候や地域の安全に関するアラートなどがあります。

地震速報とかアプリ入ってるわけでもなくアラートがなりますが、用途としてはこれと似たような感じですかね。

### 割込レベルを指定した通知の送り方

ローカル通知の場合は新しく生えてるinterruptionLevelを指定するだけです。

```swift
// Time Sensitive
// Local notification

let content = UNMutableNotificationContent()
content.title = "Urgent"
content.body = "Your account requires attention."
content.interruptionLevel = .timeSensitive // ココ

let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0, repeats: false)

let request = UNNotificationRequest(identifier: "time-sensitive—example",
                                       content: content,
                                       trigger: trigger)
```

リモート側でプッシュ通知を送る場合はinterruption-levelを指定するだけです。

```json
// Time Sensitive
// Push notification

{
    "aps" : {
        "alert" : {
            "title" : "Urgent",
            "body" : "Your account requires attention."
        }
        "interruption-level" : "time-sensitive"
    }
}
```

また、Time Sensitive通知を送る場合はXcodeで関連するcapabilityを有効にする必要があります。

## コミュニケーション通知でよりよいUXを提供

フォーカスにより、不要な通知は表示されなくなりますが、同僚や家族からなど重要な通知のみをコミュニケーション通知によって表示することができます。
しかしその通知の重要性は、ユーザーの判断によって異なります。
そこで、Siriの提案により、割り込みを許可すべき重要な人物を提案します。
この提案はユーザーが電話に出たり、FaceTimesに参加したり、メッセージを送ったりしてデバイスを操作した情報をもとに候補を知ることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42234/d15e98e5-053b-7458-cd37-5c61fd1dee71.png)

### Siriに動作を伝える実装

実装的にはどうやってSiriは重要性・関係性を判断するのでしょうか？

そこで出てくるのが、新規に追加されたINStartCallIntentとINSendMessageIntentというIntentになります。
何がコミュニケーションで、何がコミュニケーションでないかを判断するために、この新しいAPIでは、SiriKitの通話やメッセージのインテントを通知に追加することができます。

メッセージの通知を受け取ったときはINSendMessageIntentを生成してINInteractionに情報を渡してあげます。

```swift
// Create a messaging notification
// In UNNotificationServiceExtension subclass

func didReceive(_ request: UNNotificationRequest,
                withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    let incomingMessageIntent: INSendMessageIntent = // ...
    let interaction = INInteraction(intent: incomingMessageIntent, response: nil)
    interaction.direction = .incoming
    interaction.donate(completion: nil)
    do {
        let messageContent = try request.content.updating(from: incomingMessageIntent)
        contentHandler(messageContent)
    } catch {
       // Handle error
    }
}
```

電話を受け取った場合はINStartCallIntentを同じくINInteractionに渡してあげます。

```swift
// Create a call notification
// In UNNotificationServiceExtension subclass

func didReceive(_ request: UNNotificationRequest,
                withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    let incomingCallIntent: INStartCallIntent = // ...
    let interaction = INInteraction(intent: incomingCallIntent, response: nil)
    interaction.direction = .incoming
    interaction.donate(completion: nil)
    do {
        let callContent = try request.content.updating(from: incomingCallIntent)
        contentHandler(callContent)
    } catch {
       // Handle error
    }
}
```

逆にメッセージを送信する場合はdirectionを.outgoingに変えて渡してあげます。

```swift
func sendMessage(...) {
    // ...

    let intent: INSendMessageIntent = // ...
    let interaction = INInteraction(intent: intent, response: nil)

    interaction.direction = .outgoing
    interaction.donate(completion: nil)
}
```

また、Person Intentを生成して渡してあげることによって、よりよいUXを提供できるようになります。

```swift
// Create INPerson

let person = INPerson(personHandle: handle,
                    nameComponents: nameComponents,
                       displayName: displayName,
                             image: image,
                 contactIdentifier: contactIdentifier,
                  customIdentifier: customIdentifier,
                           aliases: nil,
                    suggestionType: suggestionType)


// Create INSendMessageIntent
// In your notification service extension

let intent = INSendMessageIntent(recipients: [person2],
                        outgoingMessageType: .outgoingMessageText,
                                    content: content,
                         speakableGroupName: speakableGroupName,
                     conversationIdentifier: conversationIdentifier,
                                serviceName: serviceName,
                                     sender: person1,
                                attachments: nil)

let interaction = INInteraction(intent: intent, response: nil)
interaction.direction = .incoming
```

Xcodeの設定としてはCapabilityから関連機能を有効にしたあと、Info.plistのNSUserActivityTypesにIntentタイプを追加します。
Intentを使えば、通知、共有シート、連絡先、Spotlightなどで、アプリがより見やすくなります。

LINEなどのコミュニケーションツールには入れてあげるとより便利なアプリになりそうですね。

## まとめ

- 通知がより便利な見せ方になった
- 割り込みレベルで通知をよりよくコントロールできるようになった
- Intentの実装でよりよいUXな通知になる

より一層不要通知は撲滅されて通知が見やすくなりますね！



最後に、[ワンナイト人狼オンライン](https://online.1nite-jinro.com/link/blog)というゲームを作ってます！よかったら遊んでね！

https://online.1nite-jinro.com/link/blog

他にも[Camecon](https://camecon.me/)や[Offcha](https://offcha.com/)といったサービスも作ってるのでよかったら使ってね！

https://camecon.me/

https://offcha.com/

また、チームビルディングや技術顧問、Firebaseの設計やアドバイスといったお話も受け付けてますので御用の方は弊社までお問い合わせください。

https://virapture.com/