SlideShare une entreprise Scribd logo
1  sur  50
Télécharger pour lire hors ligne
Copyright © Up-frontier, Inc. All rights reserved.
New Notification API

in iOS 10
1
Copyright © Up-frontier, Inc. All rights reserved.
アジェンダ
• 新 API でローカル通知
• Media Attachment
• Notification Content Extension
• Notification Service Extension
2
Copyright © Up-frontier, Inc. All rights reserved.
アジェンダ
• 新 API でローカル通知
• Media Attachment
• Notification Content Extension
• Notification Service Extension
3
Copyright © Up-frontier, Inc. All rights reserved.
ローカル通知⽅法
1. コンテンツを作って
2. トリガーを設定して
3. センターにリクエスト
4
Copyright © Up-frontier, Inc. All rights reserved.
ローカル通知⽅法
1. コンテンツを作って
2. トリガーを設定して
3. センターにリクエスト
5
Copyright © Up-frontier, Inc. All rights reserved.
コンテンツ
let content = UNMutableNotificationContent()
content.title = "Title"
content.subtitle = "Subtitle"
content.body = "Body"
content.categoryIdentifier = "sample-category"
6
Copyright © Up-frontier, Inc. All rights reserved.
コンテンツ
let content = UNMutableNotificationContent()
content.title = "Title"
content.subtitle = "Subtitle"
content.body = "Body"
content.categoryIdentifier = "sample-category"
7
Copyright © Up-frontier, Inc. All rights reserved.
コンテンツ
let content = UNMutableNotificationContent()
content.title = "Title"
content.subtitle = "Subtitle"
content.body = "Body"
content.categoryIdentifier = "sample-category"
8
Action や、カスタム UI など
識別に使⽤
Copyright © Up-frontier, Inc. All rights reserved.
ローカル通知⽅法
1. コンテンツを作って
2. トリガーを設定して
3. センターにリクエスト
9
Copyright © Up-frontier, Inc. All rights reserved.
トリガー
UNNotificationTrigger
10
Copyright © Up-frontier, Inc. All rights reserved.
トリガー
UNNotificationTrigger
11
let intervalTrigger = UNTimeIntervalNotificationTrigger(
timeInterval: 5, repeats: false)
Copyright © Up-frontier, Inc. All rights reserved.
リクエスト
let id = "sample-(Date().timeIntervalSince1970)"
let request = UNNotificationRequest(
identifier: id,
content: content,
trigger: intervalTrigger
)
center.add(request) { error in
if let error = error {
print("Error on requesting notification: (error)")
}
print("Finish requesting notification: (id)")
}
12
Copyright © Up-frontier, Inc. All rights reserved.
リクエスト
let id = "sample-(Date().timeIntervalSince1970)"
let request = UNNotificationRequest(
identifier: id,
content: content,
trigger: intervalTrigger
)
center.add(request) { error in
if let error = error {
print("Error on requesting notification: (error)")
}
print("Finish requesting notification: (id)")
}
13
トリガのセット
Copyright © Up-frontier, Inc. All rights reserved.
リクエスト
let id = "sample-(Date().timeIntervalSince1970)"
let request = UNNotificationRequest(
identifier: id,
content: content,
trigger: intervalTrigger
)
center.add(request) { error in
if let error = error {
print("Error on requesting notification: (error)")
}
print("Finish requesting notification: (id)")
}
14
identifier: 識別⼦、更新・削除に利⽤
contents: 通知本体
trigger: 発⽕タイミング
Copyright © Up-frontier, Inc. All rights reserved.
リクエスト
let id = "sample-(Date().timeIntervalSince1970)"
let request = UNNotificationRequest(
identifier: id,
content: content,
trigger: intervalTrigger
)
center.add(request) { error in
if let error = error {
print("Error on requesting notification: (error)")
}
print("Finish requesting notification: (id)")
}
15
Notification Centerに追加で完了
Copyright © Up-frontier, Inc. All rights reserved.
アジェンダ
• 新 API でローカル通知
• Media Attachment
• Notification Content Extension
• Notification Service Extension
16
Copyright © Up-frontier, Inc. All rights reserved.
Media Attachment
• 通知領域で様々なメディアファイルを表⽰する
17
Copyright © Up-frontier, Inc. All rights reserved.
メディア
18
画像

jpg, png, gif
⾳声

mp3, mp4
動画

mp4, avi

Copyright © Up-frontier, Inc. All rights reserved.
メディア
1. 通知内容を作って
2. トリガーを設定して
3. センターにリクエスト
19
Copyright © Up-frontier, Inc. All rights reserved.
メディア
1. 通知内容を作って
2. トリガーを設定して
3. センターにリクエスト
20
Copyright © Up-frontier, Inc. All rights reserved.
メディア
1. 通知内容を作って
2. トリガーを設定して
3. センターにリクエスト
21
ここで設定する
Copyright © Up-frontier, Inc. All rights reserved.
UNNotificationAttachment
guard let imageURL = R.file.sampleJpg() else {
print("Could not instantiate file URL!”)
return
}
let content = defaultContent
do {
let attachment = try UNNotificationAttachment(
identifier: "sample-cat",
url: imageURL,
options: nil
)
content.attachments = [attachment]
} catch(let error) {
print("Could not instantiate attachment: (error)")
}
22
Copyright © Up-frontier, Inc. All rights reserved.
UNNotificationAttachment
guard let imageURL = R.file.sampleJpg() else {
print("Could not instantiate file URL!”)
return
}
let content = defaultContent
do {
let attachment = try UNNotificationAttachment(
identifier: "sample-cat",
url: imageURL,
options: nil
)
content.attachments = [attachment]
} catch(let error) {
print("Could not instantiate attachment: (error)")
}
23
画像の設定
UNNotificationAttachmentを使う
Copyright © Up-frontier, Inc. All rights reserved.
UNNotificationAttachment
guard let imageURL = R.file.sampleJpg() else {
print("Could not instantiate file URL!”)
return
}
let content = defaultContent
do {
let attachment = try UNNotificationAttachment(
identifier: "sample-cat",
url: imageURL,
options: nil
)
content.attachments = [attachment]
} catch(let error) {
print("Could not instantiate attachment: (error)")
}
24
コンテンツに指定
Copyright © Up-frontier, Inc. All rights reserved.
アジェンダ
• 新 API でローカル通知
• Media Attachment
• Notification Content Extension
• Notification Service Extension
25
Copyright © Up-frontier, Inc. All rights reserved.
Notification Content Extension
• オリジナルUIの通知を作成する
26
Copyright © Up-frontier, Inc. All rights reserved.
ターゲット
• Notification Content Extension
27
Copyright © Up-frontier, Inc. All rights reserved.
カスタム UI
• Storyboard を使って UI を構築
28
Copyright © Up-frontier, Inc. All rights reserved.
NotificationViewController
class NotificationViewController
: UIViewController
, UNNotificationContentExtension {
@IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
}
func didReceive(_ notification: UNNotification) {
let content = notification.request.content
if
let lat = content.userInfo["latitude"] as? Double,
let lon = content.userInfo["longitude"] as? Double
{
putPin(into: CLLocationCoordinate2D(latitude: lat, longitude:
lon))
}
}
}
29
Copyright © Up-frontier, Inc. All rights reserved.
NotificationViewController
class NotificationViewController
: UIViewController
, UNNotificationContentExtension {
@IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
}
func didReceive(_ notification: UNNotification) {
let content = notification.request.content
if
let lat = content.userInfo["latitude"] as? Double,
let lon = content.userInfo["longitude"] as? Double
{
putPin(into: CLLocationCoordinate2D(latitude: lat, longitude:
lon))
}
}
}
30
MapViewを持った通知
Copyright © Up-frontier, Inc. All rights reserved.
NotificationViewController
class NotificationViewController
: UIViewController
, UNNotificationContentExtension {
@IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
}
func didReceive(_ notification: UNNotification) {
let content = notification.request.content
if
let lat = content.userInfo["latitude"] as? Double,
let lon = content.userInfo["longitude"] as? Double
{
putPin(into: CLLocationCoordinate2D(latitude: lat, longitude:
lon))
}
}
}
31
通知が発⽕した時の処理
Copyright © Up-frontier, Inc. All rights reserved.
NotificationViewController
class NotificationViewController
: UIViewController
, UNNotificationContentExtension {
@IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
}
func didReceive(_ notification: UNNotification) {
let content = notification.request.content
if
let lat = content.userInfo["latitude"] as? Double,
let lon = content.userInfo["longitude"] as? Double
{
putPin(into: CLLocationCoordinate2D(latitude: lat, longitude:
lon))
}
}
}
32
通知が発⽕した時の処理
通知内容を反映
Copyright © Up-frontier, Inc. All rights reserved.
Info.plist
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>UNNotificationExtensionCategory</key>
<array>
<string>customUI</string>
</array>
<key>UNNotificationExtensionInitialContentSizeRatio</key>
<real>1</real>
<key>UNNotificationExtensionDefaultContentHidden</key>
<true/>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.content-extension</
string>
</dict> 33
Copyright © Up-frontier, Inc. All rights reserved.
Info.plist
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>UNNotificationExtensionCategory</key>
<array>
<string>customUI</string>
</array>
<key>UNNotificationExtensionInitialContentSizeRatio</key>
<real>1</real>
<key>UNNotificationExtensionDefaultContentHidden</key>
<true/>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.content-extension</
string>
</dict> 34
categoryIdentifierと
⼀致している必要あり
Copyright © Up-frontier, Inc. All rights reserved.
Info.plist
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>UNNotificationExtensionCategory</key>
<array>
<string>customUI</string>
</array>
<key>UNNotificationExtensionInitialContentSizeRatio</key>
<real>1</real>
<key>UNNotificationExtensionDefaultContentHidden</key>
<true/>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.content-extension</
string>
</dict> 35
UIの縦横⽐
Copyright © Up-frontier, Inc. All rights reserved.
Info.plist
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>UNNotificationExtensionCategory</key>
<array>
<string>customUI</string>
</array>
<key>UNNotificationExtensionInitialContentSizeRatio</key>
<real>1</real>
<key>UNNotificationExtensionDefaultContentHidden</key>
<true/>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.content-extension</
string>
</dict> 36
デフォルトのタイトル、サブタイ
トル、コンテンツ
Copyright © Up-frontier, Inc. All rights reserved.
アジェンダ
• 新 API でローカル通知
• Media Attachment
• Notification Content Extension
• Notification Service Extension
37
Copyright © Up-frontier, Inc. All rights reserved.
Notification Service Extension
• オリジナルUIの通知を作成する
38
Copyright © Up-frontier, Inc. All rights reserved.
ターゲット
• Notification Service Extension
39
Copyright © Up-frontier, Inc. All rights reserved.
NotificationService
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest,
withContentHandler contentHandler:
@escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
// Modify the notification content here...
bestAttemptContent.title = "(bestAttemptContent.title) [modified]"
contentHandler(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content,
// otherwise the original push payload will be used.
if
let contentHandler = contentHandler,
let bestAttemptContent = bestAttemptContent {
bestAttemptContent.title = "(bestAttemptContent.title) [Time Expired]"
contentHandler(bestAttemptContent)
}
}
}
40
Copyright © Up-frontier, Inc. All rights reserved.
NotificationService
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest,
withContentHandler contentHandler:
@escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
// Modify the notification content here...
bestAttemptContent.title = "(bestAttemptContent.title) [modified]"
contentHandler(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content,
// otherwise the original push payload will be used.
if
let contentHandler = contentHandler,
let bestAttemptContent = bestAttemptContent {
bestAttemptContent.title = "(bestAttemptContent.title) [Time Expired]"
contentHandler(bestAttemptContent)
}
}
}
41
通知内容を編集
Copyright © Up-frontier, Inc. All rights reserved.
NotificationService
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest,
withContentHandler contentHandler:
@escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
// Modify the notification content here...
bestAttemptContent.title = "(bestAttemptContent.title) [modified]"
contentHandler(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content,
// otherwise the original push payload will be used.
if
let contentHandler = contentHandler,
let bestAttemptContent = bestAttemptContent {
bestAttemptContent.title = "(bestAttemptContent.title) [Time Expired]"
contentHandler(bestAttemptContent)
}
}
}
42
処理が既定秒数(MAX30秒)を超えると呼ばれる
Copyright © Up-frontier, Inc. All rights reserved.
NotificationService
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest,
withContentHandler contentHandler:
@escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
// Modify the notification content here...
bestAttemptContent.title = "(bestAttemptContent.title) [modified]"
contentHandler(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content,
// otherwise the original push payload will be used.
if
let contentHandler = contentHandler,
let bestAttemptContent = bestAttemptContent {
bestAttemptContent.title = "(bestAttemptContent.title) [Time Expired]"
contentHandler(bestAttemptContent)
}
}
}
43
サーバからのリモートプッシュを変更する場合
mutable-contetの設定忘れないように
Copyright © Up-frontier, Inc. All rights reserved.
NotificationService
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest,
withContentHandler contentHandler:
@escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
// Modify the notification content here...
bestAttemptContent.title = "(bestAttemptContent.title) [modified]"
contentHandler(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content,
// otherwise the original push payload will be used.
if
let contentHandler = contentHandler,
let bestAttemptContent = bestAttemptContent {
bestAttemptContent.title = "(bestAttemptContent.title) [Time Expired]"
contentHandler(bestAttemptContent)
}
}
}
44
処理が終わったら速やかに呼ぶ
Copyright © Up-frontier, Inc. All rights reserved.
ありそうな使い⽅
• ペイロードにサーバにあるメディアの URL
• ダウンロードして
• Media Attachment
45
Copyright © Up-frontier, Inc. All rights reserved.
リモートコンテンツ
貼り付け
if let bestAttemptContent = bestAttemptContent {
guard
let urlString = bestAttemptContent.userInfo["remote-url"] as? String,
let remoteURL = URL(string: urlString)
else {
contentHandler(bestAttemptContent)
return
}
download(remoteURL) { url, error in
guard let fileURL = url else {
print(error)
contentHandler(bestAttemptContent)
return
}
do {
let attachment = try UNNotificationAttachment(
identifier: "remote", url: fileURL
)
bestAttemptContent.attachments = [attachment]
contentHandler(bestAttemptContent)
} catch(let e) {
print(e)
contentHandler(bestAttemptContent)
}
}
}
46
Copyright © Up-frontier, Inc. All rights reserved.
リモートコンテンツ
貼り付け
if let bestAttemptContent = bestAttemptContent {
guard
let urlString = bestAttemptContent.userInfo["remote-url"] as? String,
let remoteURL = URL(string: urlString)
else {
contentHandler(bestAttemptContent)
return
}
download(remoteURL) { url, error in
guard let fileURL = url else {
print(error)
contentHandler(bestAttemptContent)
return
}
do {
let attachment = try UNNotificationAttachment(
identifier: "remote", url: fileURL
)
bestAttemptContent.attachments = [attachment]
contentHandler(bestAttemptContent)
} catch(let e) {
print(e)
contentHandler(bestAttemptContent)
}
}
}
47
画像のダウンロード
30秒超えるかも
Copyright © Up-frontier, Inc. All rights reserved.
serviceExtensionTimeWillExpire
override func serviceExtensionTimeWillExpire() {
if
let contentHandler = contentHandler,
let bestAttemptContent = bestAttemptContent
{
bestAttemptContent.title += " [頑張ったけれどダメだっ
たよ]"
contentHandler(bestAttemptContent)
}
}
48
Copyright © Up-frontier, Inc. All rights reserved.
serviceExtensionTimeWillExpire
override func serviceExtensionTimeWillExpire() {
if
let contentHandler = contentHandler,
let bestAttemptContent = bestAttemptContent
{
bestAttemptContent.title += " [頑張ったけれどダメだっ
たよ]"
contentHandler(bestAttemptContent)
}
}
49
サーバからのリモートプッシュの内容を
変更しない場合
もとのペイロードのまま表⽰されてしまうので注意
Copyright © Up-frontier, Inc. All rights reserved.
まとめ
• 新 API でローカル通知
• Media Attachment
• Notification Content Extension
• Notification Service Extension
50

Contenu connexe

Similaire à New Notification API in iOS 10

WebAPIs & Apps - Mozilla London
WebAPIs & Apps - Mozilla LondonWebAPIs & Apps - Mozilla London
WebAPIs & Apps - Mozilla London
Robert Nyman
 
Web APIs & Apps - Mozilla
Web APIs & Apps - MozillaWeb APIs & Apps - Mozilla
Web APIs & Apps - Mozilla
Robert Nyman
 

Similaire à New Notification API in iOS 10 (20)

Protecting Microservices APIs with 42Crunch API Firewall
Protecting Microservices APIs with 42Crunch API FirewallProtecting Microservices APIs with 42Crunch API Firewall
Protecting Microservices APIs with 42Crunch API Firewall
 
Creating Rich Server API’s for your Mobile Apps - Best Practices and Guidelines
Creating Rich Server API’s for your Mobile Apps - Best Practices and GuidelinesCreating Rich Server API’s for your Mobile Apps - Best Practices and Guidelines
Creating Rich Server API’s for your Mobile Apps - Best Practices and Guidelines
 
Advanced REST API Scripting With AppDynamics
Advanced REST API Scripting With AppDynamicsAdvanced REST API Scripting With AppDynamics
Advanced REST API Scripting With AppDynamics
 
The Powerful and Comprehensive API for Mobile App Development and Testing
The Powerful and Comprehensive API for Mobile App Development and TestingThe Powerful and Comprehensive API for Mobile App Development and Testing
The Powerful and Comprehensive API for Mobile App Development and Testing
 
Push Notification
Push NotificationPush Notification
Push Notification
 
API-first, going beyond SOA, ESB & Integration
API-first, going beyond SOA, ESB & IntegrationAPI-first, going beyond SOA, ESB & Integration
API-first, going beyond SOA, ESB & Integration
 
INTERFACE, by apidays - Lessons learned from implementing our custom ‘Big Da...
INTERFACE, by apidays  - Lessons learned from implementing our custom ‘Big Da...INTERFACE, by apidays  - Lessons learned from implementing our custom ‘Big Da...
INTERFACE, by apidays - Lessons learned from implementing our custom ‘Big Da...
 
OpenStack Architecture
OpenStack ArchitectureOpenStack Architecture
OpenStack Architecture
 
OpenStack Architecture
OpenStack ArchitectureOpenStack Architecture
OpenStack Architecture
 
InfluxDB Client Libraries and Applications | Miroslav Malecha | Bonitoo
InfluxDB Client Libraries and Applications | Miroslav Malecha | BonitooInfluxDB Client Libraries and Applications | Miroslav Malecha | Bonitoo
InfluxDB Client Libraries and Applications | Miroslav Malecha | Bonitoo
 
Design - Start Your API Journey Today
Design - Start Your API Journey TodayDesign - Start Your API Journey Today
Design - Start Your API Journey Today
 
Apache Cordova phonegap plugins for mobile app development
Apache Cordova phonegap plugins for mobile app developmentApache Cordova phonegap plugins for mobile app development
Apache Cordova phonegap plugins for mobile app development
 
Progetta, crea e gestisci Modern Application per web e mobile su AWS
Progetta, crea e gestisci Modern Application per web e mobile su AWSProgetta, crea e gestisci Modern Application per web e mobile su AWS
Progetta, crea e gestisci Modern Application per web e mobile su AWS
 
WebAPIs & Apps - Mozilla London
WebAPIs & Apps - Mozilla LondonWebAPIs & Apps - Mozilla London
WebAPIs & Apps - Mozilla London
 
What Does API Monitoring Mean for Product Managers?
What Does API Monitoring Mean for Product Managers?What Does API Monitoring Mean for Product Managers?
What Does API Monitoring Mean for Product Managers?
 
API Services: Building State-of-the-Art APIs
API Services: Building State-of-the-Art APIsAPI Services: Building State-of-the-Art APIs
API Services: Building State-of-the-Art APIs
 
Introduction to tvOS app Development !
Introduction to tvOS app Development !Introduction to tvOS app Development !
Introduction to tvOS app Development !
 
Securely expose protected resources as ap is with app42 api gateway
Securely expose protected resources as ap is with app42 api gatewaySecurely expose protected resources as ap is with app42 api gateway
Securely expose protected resources as ap is with app42 api gateway
 
Web APIs & Apps - Mozilla
Web APIs & Apps - MozillaWeb APIs & Apps - Mozilla
Web APIs & Apps - Mozilla
 
I Love APIs 2015: Crash Course Foundational Topics in Apigee Edge Workshop
I Love APIs 2015: Crash Course Foundational Topics in Apigee Edge WorkshopI Love APIs 2015: Crash Course Foundational Topics in Apigee Edge Workshop
I Love APIs 2015: Crash Course Foundational Topics in Apigee Edge Workshop
 

Plus de Gaprot

Salmon Hunt
Salmon HuntSalmon Hunt
Salmon Hunt
Gaprot
 
SONY Camera Remote API
SONY Camera Remote APISONY Camera Remote API
SONY Camera Remote API
Gaprot
 

Plus de Gaprot (14)

AR開発高速化!「CFA」作りました!
AR開発高速化!「CFA」作りました!AR開発高速化!「CFA」作りました!
AR開発高速化!「CFA」作りました!
 
Unity + iOS/Android VR ことはじめ
Unity + iOS/Android VR ことはじめUnity + iOS/Android VR ことはじめ
Unity + iOS/Android VR ことはじめ
 
1201 ギャップロ軍団企画書
1201 ギャップロ軍団企画書1201 ギャップロ軍団企画書
1201 ギャップロ軍団企画書
 
Speech Framework
Speech FrameworkSpeech Framework
Speech Framework
 
SiriKit iOS10
SiriKit iOS10SiriKit iOS10
SiriKit iOS10
 
Proactive Suggestions
Proactive SuggestionsProactive Suggestions
Proactive Suggestions
 
iOS 10 new Camera
iOS 10 new CameraiOS 10 new Camera
iOS 10 new Camera
 
HTML5 + JavaScriptでDRMつきMPEG-DASHを再生させる
HTML5 + JavaScriptでDRMつきMPEG-DASHを再生させるHTML5 + JavaScriptでDRMつきMPEG-DASHを再生させる
HTML5 + JavaScriptでDRMつきMPEG-DASHを再生させる
 
Aiマッシュアップ委員会 仕様説明資料
Aiマッシュアップ委員会 仕様説明資料Aiマッシュアップ委員会 仕様説明資料
Aiマッシュアップ委員会 仕様説明資料
 
GoF のデザインパターンじゃないけど、よくあるパターン
GoF のデザインパターンじゃないけど、よくあるパターンGoF のデザインパターンじゃないけど、よくあるパターン
GoF のデザインパターンじゃないけど、よくあるパターン
 
Java の Collection 関連について整理してみました
Java の Collection 関連について整理してみましたJava の Collection 関連について整理してみました
Java の Collection 関連について整理してみました
 
Salmon Hunt
Salmon HuntSalmon Hunt
Salmon Hunt
 
SONY Camera Remote API
SONY Camera Remote APISONY Camera Remote API
SONY Camera Remote API
 
「バグあるある」と「仕様変更あるある」一挙大放出SP!
「バグあるある」と「仕様変更あるある」一挙大放出SP!「バグあるある」と「仕様変更あるある」一挙大放出SP!
「バグあるある」と「仕様変更あるある」一挙大放出SP!
 

Dernier

Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
Cara Menggugurkan Kandungan 087776558899
 

Dernier (8)

Thane 💋 Call Girls 7738631006 💋 Call Girls in Thane Escort service book now. ...
Thane 💋 Call Girls 7738631006 💋 Call Girls in Thane Escort service book now. ...Thane 💋 Call Girls 7738631006 💋 Call Girls in Thane Escort service book now. ...
Thane 💋 Call Girls 7738631006 💋 Call Girls in Thane Escort service book now. ...
 
Android Application Components with Implementation & Examples
Android Application Components with Implementation & ExamplesAndroid Application Components with Implementation & Examples
Android Application Components with Implementation & Examples
 
Mobile Application Development-Android and It’s Tools
Mobile Application Development-Android and It’s ToolsMobile Application Development-Android and It’s Tools
Mobile Application Development-Android and It’s Tools
 
Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
 
9999266834 Call Girls In Noida Sector 52 (Delhi) Call Girl Service
9999266834 Call Girls In Noida Sector 52 (Delhi) Call Girl Service9999266834 Call Girls In Noida Sector 52 (Delhi) Call Girl Service
9999266834 Call Girls In Noida Sector 52 (Delhi) Call Girl Service
 
Mobile Application Development-Components and Layouts
Mobile Application Development-Components and LayoutsMobile Application Development-Components and Layouts
Mobile Application Development-Components and Layouts
 
Leading Mobile App Development Companies in India (2).pdf
Leading Mobile App Development Companies in India (2).pdfLeading Mobile App Development Companies in India (2).pdf
Leading Mobile App Development Companies in India (2).pdf
 
FULL ENJOY - 9999218229 Call Girls in {Mahipalpur}| Delhi NCR
FULL ENJOY - 9999218229 Call Girls in {Mahipalpur}| Delhi NCRFULL ENJOY - 9999218229 Call Girls in {Mahipalpur}| Delhi NCR
FULL ENJOY - 9999218229 Call Girls in {Mahipalpur}| Delhi NCR
 

New Notification API in iOS 10

  • 1. Copyright © Up-frontier, Inc. All rights reserved. New Notification API
 in iOS 10 1
  • 2. Copyright © Up-frontier, Inc. All rights reserved. アジェンダ • 新 API でローカル通知 • Media Attachment • Notification Content Extension • Notification Service Extension 2
  • 3. Copyright © Up-frontier, Inc. All rights reserved. アジェンダ • 新 API でローカル通知 • Media Attachment • Notification Content Extension • Notification Service Extension 3
  • 4. Copyright © Up-frontier, Inc. All rights reserved. ローカル通知⽅法 1. コンテンツを作って 2. トリガーを設定して 3. センターにリクエスト 4
  • 5. Copyright © Up-frontier, Inc. All rights reserved. ローカル通知⽅法 1. コンテンツを作って 2. トリガーを設定して 3. センターにリクエスト 5
  • 6. Copyright © Up-frontier, Inc. All rights reserved. コンテンツ let content = UNMutableNotificationContent() content.title = "Title" content.subtitle = "Subtitle" content.body = "Body" content.categoryIdentifier = "sample-category" 6
  • 7. Copyright © Up-frontier, Inc. All rights reserved. コンテンツ let content = UNMutableNotificationContent() content.title = "Title" content.subtitle = "Subtitle" content.body = "Body" content.categoryIdentifier = "sample-category" 7
  • 8. Copyright © Up-frontier, Inc. All rights reserved. コンテンツ let content = UNMutableNotificationContent() content.title = "Title" content.subtitle = "Subtitle" content.body = "Body" content.categoryIdentifier = "sample-category" 8 Action や、カスタム UI など 識別に使⽤
  • 9. Copyright © Up-frontier, Inc. All rights reserved. ローカル通知⽅法 1. コンテンツを作って 2. トリガーを設定して 3. センターにリクエスト 9
  • 10. Copyright © Up-frontier, Inc. All rights reserved. トリガー UNNotificationTrigger 10
  • 11. Copyright © Up-frontier, Inc. All rights reserved. トリガー UNNotificationTrigger 11 let intervalTrigger = UNTimeIntervalNotificationTrigger( timeInterval: 5, repeats: false)
  • 12. Copyright © Up-frontier, Inc. All rights reserved. リクエスト let id = "sample-(Date().timeIntervalSince1970)" let request = UNNotificationRequest( identifier: id, content: content, trigger: intervalTrigger ) center.add(request) { error in if let error = error { print("Error on requesting notification: (error)") } print("Finish requesting notification: (id)") } 12
  • 13. Copyright © Up-frontier, Inc. All rights reserved. リクエスト let id = "sample-(Date().timeIntervalSince1970)" let request = UNNotificationRequest( identifier: id, content: content, trigger: intervalTrigger ) center.add(request) { error in if let error = error { print("Error on requesting notification: (error)") } print("Finish requesting notification: (id)") } 13 トリガのセット
  • 14. Copyright © Up-frontier, Inc. All rights reserved. リクエスト let id = "sample-(Date().timeIntervalSince1970)" let request = UNNotificationRequest( identifier: id, content: content, trigger: intervalTrigger ) center.add(request) { error in if let error = error { print("Error on requesting notification: (error)") } print("Finish requesting notification: (id)") } 14 identifier: 識別⼦、更新・削除に利⽤ contents: 通知本体 trigger: 発⽕タイミング
  • 15. Copyright © Up-frontier, Inc. All rights reserved. リクエスト let id = "sample-(Date().timeIntervalSince1970)" let request = UNNotificationRequest( identifier: id, content: content, trigger: intervalTrigger ) center.add(request) { error in if let error = error { print("Error on requesting notification: (error)") } print("Finish requesting notification: (id)") } 15 Notification Centerに追加で完了
  • 16. Copyright © Up-frontier, Inc. All rights reserved. アジェンダ • 新 API でローカル通知 • Media Attachment • Notification Content Extension • Notification Service Extension 16
  • 17. Copyright © Up-frontier, Inc. All rights reserved. Media Attachment • 通知領域で様々なメディアファイルを表⽰する 17
  • 18. Copyright © Up-frontier, Inc. All rights reserved. メディア 18 画像
 jpg, png, gif ⾳声
 mp3, mp4 動画
 mp4, avi

  • 19. Copyright © Up-frontier, Inc. All rights reserved. メディア 1. 通知内容を作って 2. トリガーを設定して 3. センターにリクエスト 19
  • 20. Copyright © Up-frontier, Inc. All rights reserved. メディア 1. 通知内容を作って 2. トリガーを設定して 3. センターにリクエスト 20
  • 21. Copyright © Up-frontier, Inc. All rights reserved. メディア 1. 通知内容を作って 2. トリガーを設定して 3. センターにリクエスト 21 ここで設定する
  • 22. Copyright © Up-frontier, Inc. All rights reserved. UNNotificationAttachment guard let imageURL = R.file.sampleJpg() else { print("Could not instantiate file URL!”) return } let content = defaultContent do { let attachment = try UNNotificationAttachment( identifier: "sample-cat", url: imageURL, options: nil ) content.attachments = [attachment] } catch(let error) { print("Could not instantiate attachment: (error)") } 22
  • 23. Copyright © Up-frontier, Inc. All rights reserved. UNNotificationAttachment guard let imageURL = R.file.sampleJpg() else { print("Could not instantiate file URL!”) return } let content = defaultContent do { let attachment = try UNNotificationAttachment( identifier: "sample-cat", url: imageURL, options: nil ) content.attachments = [attachment] } catch(let error) { print("Could not instantiate attachment: (error)") } 23 画像の設定 UNNotificationAttachmentを使う
  • 24. Copyright © Up-frontier, Inc. All rights reserved. UNNotificationAttachment guard let imageURL = R.file.sampleJpg() else { print("Could not instantiate file URL!”) return } let content = defaultContent do { let attachment = try UNNotificationAttachment( identifier: "sample-cat", url: imageURL, options: nil ) content.attachments = [attachment] } catch(let error) { print("Could not instantiate attachment: (error)") } 24 コンテンツに指定
  • 25. Copyright © Up-frontier, Inc. All rights reserved. アジェンダ • 新 API でローカル通知 • Media Attachment • Notification Content Extension • Notification Service Extension 25
  • 26. Copyright © Up-frontier, Inc. All rights reserved. Notification Content Extension • オリジナルUIの通知を作成する 26
  • 27. Copyright © Up-frontier, Inc. All rights reserved. ターゲット • Notification Content Extension 27
  • 28. Copyright © Up-frontier, Inc. All rights reserved. カスタム UI • Storyboard を使って UI を構築 28
  • 29. Copyright © Up-frontier, Inc. All rights reserved. NotificationViewController class NotificationViewController : UIViewController , UNNotificationContentExtension { @IBOutlet weak var mapView: MKMapView! override func viewDidLoad() { super.viewDidLoad() } func didReceive(_ notification: UNNotification) { let content = notification.request.content if let lat = content.userInfo["latitude"] as? Double, let lon = content.userInfo["longitude"] as? Double { putPin(into: CLLocationCoordinate2D(latitude: lat, longitude: lon)) } } } 29
  • 30. Copyright © Up-frontier, Inc. All rights reserved. NotificationViewController class NotificationViewController : UIViewController , UNNotificationContentExtension { @IBOutlet weak var mapView: MKMapView! override func viewDidLoad() { super.viewDidLoad() } func didReceive(_ notification: UNNotification) { let content = notification.request.content if let lat = content.userInfo["latitude"] as? Double, let lon = content.userInfo["longitude"] as? Double { putPin(into: CLLocationCoordinate2D(latitude: lat, longitude: lon)) } } } 30 MapViewを持った通知
  • 31. Copyright © Up-frontier, Inc. All rights reserved. NotificationViewController class NotificationViewController : UIViewController , UNNotificationContentExtension { @IBOutlet weak var mapView: MKMapView! override func viewDidLoad() { super.viewDidLoad() } func didReceive(_ notification: UNNotification) { let content = notification.request.content if let lat = content.userInfo["latitude"] as? Double, let lon = content.userInfo["longitude"] as? Double { putPin(into: CLLocationCoordinate2D(latitude: lat, longitude: lon)) } } } 31 通知が発⽕した時の処理
  • 32. Copyright © Up-frontier, Inc. All rights reserved. NotificationViewController class NotificationViewController : UIViewController , UNNotificationContentExtension { @IBOutlet weak var mapView: MKMapView! override func viewDidLoad() { super.viewDidLoad() } func didReceive(_ notification: UNNotification) { let content = notification.request.content if let lat = content.userInfo["latitude"] as? Double, let lon = content.userInfo["longitude"] as? Double { putPin(into: CLLocationCoordinate2D(latitude: lat, longitude: lon)) } } } 32 通知が発⽕した時の処理 通知内容を反映
  • 33. Copyright © Up-frontier, Inc. All rights reserved. Info.plist <dict> <key>NSExtensionAttributes</key> <dict> <key>UNNotificationExtensionCategory</key> <array> <string>customUI</string> </array> <key>UNNotificationExtensionInitialContentSizeRatio</key> <real>1</real> <key>UNNotificationExtensionDefaultContentHidden</key> <true/> </dict> <key>NSExtensionMainStoryboard</key> <string>MainInterface</string> <key>NSExtensionPointIdentifier</key> <string>com.apple.usernotifications.content-extension</ string> </dict> 33
  • 34. Copyright © Up-frontier, Inc. All rights reserved. Info.plist <dict> <key>NSExtensionAttributes</key> <dict> <key>UNNotificationExtensionCategory</key> <array> <string>customUI</string> </array> <key>UNNotificationExtensionInitialContentSizeRatio</key> <real>1</real> <key>UNNotificationExtensionDefaultContentHidden</key> <true/> </dict> <key>NSExtensionMainStoryboard</key> <string>MainInterface</string> <key>NSExtensionPointIdentifier</key> <string>com.apple.usernotifications.content-extension</ string> </dict> 34 categoryIdentifierと ⼀致している必要あり
  • 35. Copyright © Up-frontier, Inc. All rights reserved. Info.plist <dict> <key>NSExtensionAttributes</key> <dict> <key>UNNotificationExtensionCategory</key> <array> <string>customUI</string> </array> <key>UNNotificationExtensionInitialContentSizeRatio</key> <real>1</real> <key>UNNotificationExtensionDefaultContentHidden</key> <true/> </dict> <key>NSExtensionMainStoryboard</key> <string>MainInterface</string> <key>NSExtensionPointIdentifier</key> <string>com.apple.usernotifications.content-extension</ string> </dict> 35 UIの縦横⽐
  • 36. Copyright © Up-frontier, Inc. All rights reserved. Info.plist <dict> <key>NSExtensionAttributes</key> <dict> <key>UNNotificationExtensionCategory</key> <array> <string>customUI</string> </array> <key>UNNotificationExtensionInitialContentSizeRatio</key> <real>1</real> <key>UNNotificationExtensionDefaultContentHidden</key> <true/> </dict> <key>NSExtensionMainStoryboard</key> <string>MainInterface</string> <key>NSExtensionPointIdentifier</key> <string>com.apple.usernotifications.content-extension</ string> </dict> 36 デフォルトのタイトル、サブタイ トル、コンテンツ
  • 37. Copyright © Up-frontier, Inc. All rights reserved. アジェンダ • 新 API でローカル通知 • Media Attachment • Notification Content Extension • Notification Service Extension 37
  • 38. Copyright © Up-frontier, Inc. All rights reserved. Notification Service Extension • オリジナルUIの通知を作成する 38
  • 39. Copyright © Up-frontier, Inc. All rights reserved. ターゲット • Notification Service Extension 39
  • 40. Copyright © Up-frontier, Inc. All rights reserved. NotificationService class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) if let bestAttemptContent = bestAttemptContent { // Modify the notification content here... bestAttemptContent.title = "(bestAttemptContent.title) [modified]" contentHandler(bestAttemptContent) } } override func serviceExtensionTimeWillExpire() { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, // otherwise the original push payload will be used. if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { bestAttemptContent.title = "(bestAttemptContent.title) [Time Expired]" contentHandler(bestAttemptContent) } } } 40
  • 41. Copyright © Up-frontier, Inc. All rights reserved. NotificationService class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) if let bestAttemptContent = bestAttemptContent { // Modify the notification content here... bestAttemptContent.title = "(bestAttemptContent.title) [modified]" contentHandler(bestAttemptContent) } } override func serviceExtensionTimeWillExpire() { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, // otherwise the original push payload will be used. if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { bestAttemptContent.title = "(bestAttemptContent.title) [Time Expired]" contentHandler(bestAttemptContent) } } } 41 通知内容を編集
  • 42. Copyright © Up-frontier, Inc. All rights reserved. NotificationService class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) if let bestAttemptContent = bestAttemptContent { // Modify the notification content here... bestAttemptContent.title = "(bestAttemptContent.title) [modified]" contentHandler(bestAttemptContent) } } override func serviceExtensionTimeWillExpire() { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, // otherwise the original push payload will be used. if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { bestAttemptContent.title = "(bestAttemptContent.title) [Time Expired]" contentHandler(bestAttemptContent) } } } 42 処理が既定秒数(MAX30秒)を超えると呼ばれる
  • 43. Copyright © Up-frontier, Inc. All rights reserved. NotificationService class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) if let bestAttemptContent = bestAttemptContent { // Modify the notification content here... bestAttemptContent.title = "(bestAttemptContent.title) [modified]" contentHandler(bestAttemptContent) } } override func serviceExtensionTimeWillExpire() { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, // otherwise the original push payload will be used. if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { bestAttemptContent.title = "(bestAttemptContent.title) [Time Expired]" contentHandler(bestAttemptContent) } } } 43 サーバからのリモートプッシュを変更する場合 mutable-contetの設定忘れないように
  • 44. Copyright © Up-frontier, Inc. All rights reserved. NotificationService class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) if let bestAttemptContent = bestAttemptContent { // Modify the notification content here... bestAttemptContent.title = "(bestAttemptContent.title) [modified]" contentHandler(bestAttemptContent) } } override func serviceExtensionTimeWillExpire() { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, // otherwise the original push payload will be used. if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { bestAttemptContent.title = "(bestAttemptContent.title) [Time Expired]" contentHandler(bestAttemptContent) } } } 44 処理が終わったら速やかに呼ぶ
  • 45. Copyright © Up-frontier, Inc. All rights reserved. ありそうな使い⽅ • ペイロードにサーバにあるメディアの URL • ダウンロードして • Media Attachment 45
  • 46. Copyright © Up-frontier, Inc. All rights reserved. リモートコンテンツ 貼り付け if let bestAttemptContent = bestAttemptContent { guard let urlString = bestAttemptContent.userInfo["remote-url"] as? String, let remoteURL = URL(string: urlString) else { contentHandler(bestAttemptContent) return } download(remoteURL) { url, error in guard let fileURL = url else { print(error) contentHandler(bestAttemptContent) return } do { let attachment = try UNNotificationAttachment( identifier: "remote", url: fileURL ) bestAttemptContent.attachments = [attachment] contentHandler(bestAttemptContent) } catch(let e) { print(e) contentHandler(bestAttemptContent) } } } 46
  • 47. Copyright © Up-frontier, Inc. All rights reserved. リモートコンテンツ 貼り付け if let bestAttemptContent = bestAttemptContent { guard let urlString = bestAttemptContent.userInfo["remote-url"] as? String, let remoteURL = URL(string: urlString) else { contentHandler(bestAttemptContent) return } download(remoteURL) { url, error in guard let fileURL = url else { print(error) contentHandler(bestAttemptContent) return } do { let attachment = try UNNotificationAttachment( identifier: "remote", url: fileURL ) bestAttemptContent.attachments = [attachment] contentHandler(bestAttemptContent) } catch(let e) { print(e) contentHandler(bestAttemptContent) } } } 47 画像のダウンロード 30秒超えるかも
  • 48. Copyright © Up-frontier, Inc. All rights reserved. serviceExtensionTimeWillExpire override func serviceExtensionTimeWillExpire() { if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { bestAttemptContent.title += " [頑張ったけれどダメだっ たよ]" contentHandler(bestAttemptContent) } } 48
  • 49. Copyright © Up-frontier, Inc. All rights reserved. serviceExtensionTimeWillExpire override func serviceExtensionTimeWillExpire() { if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { bestAttemptContent.title += " [頑張ったけれどダメだっ たよ]" contentHandler(bestAttemptContent) } } 49 サーバからのリモートプッシュの内容を 変更しない場合 もとのペイロードのまま表⽰されてしまうので注意
  • 50. Copyright © Up-frontier, Inc. All rights reserved. まとめ • 新 API でローカル通知 • Media Attachment • Notification Content Extension • Notification Service Extension 50