Today Extension
App Extension Type
であるToday
は、通知センターにビューに置いて表示内容を即座に更新したり、簡単なタスクを実行することができWidgets
とも呼ばれる。その日の株価や天気、スケジュールやタスクなどを簡単に示し、ユーザが簡単に情報を確認することができる。
画像引用:iOS Human Interface Guidelines / Widgets
導入
Extension
用のTarget
作成する(File > New > Target > Today Extension
)
作成したExtensionのディレクトリ配下に3つのファイルが作成される。
TodayViewController.swift
MainInterface.storyboard
Info.plist
Info.plist
- NSExtensionPrincipalClass
- デフォルトではTodayViewControllerだが、独自でViewControllerを設定したい場合は、このキーにクラス名を指定する
- NSExtensionMainStoryboard
- TodayExtensionで使用するstoryboardファイルを設定するキー。デフォルトではMainInterface.storyboardが設定されている。storyboardファイルを使用したくない場合は、このキーを削除し、代わりにNSExtensionPrincipalClassを設定する
- CFBundleName
- Widgetsのタイトル名。デフォルトでは、ターゲット名が設定される。変更したい場合はこのキーを変更する
UIの作成
Widgets
で表示するUIはMainInterface.storyboard
で作成する。コードによって作成する場合は上記のようにNSExtensionMainStoryboard
キーを削除し、NSExtensionPrincipalClass
のキーにクラス名を設定する。
表示されるWidgets
の高さはOSによって自動で決められているが、widgetLargestAvailableDisplayMode
を設定することによって、Show More
のボタンが追加され、拡大時の高さを変えられるようになる。(幅は変えられない)
@available(iOS 10.0, *)
public enum NCWidgetDisplayMode : Int {
case compact // Fixed height
case expanded // Variable height
}
// expandedで拡大可能に
self.extensionContext?.widgetLargestAvailableDisplayMode = .expanded
// NCWidgetDisplayModeが変更されるたびに呼ばれるので、ここで拡大時の高さを設定する。
func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) {
if case .compact = activeDisplayMode {
preferredContentSize = maxSize
} else {
preferredContentSize.height = 250
}
}
幅を変える度に、NCWidgetDisplayMode
が変更されるので、widgetActiveDisplayModeDidChange(_:withMaximumSize:)でそれぞれの高さの設定を行う。
アニメーション
表示時にアニメーション処理を行いたい場合は、viewWillTransitionToSize:withTransitionCoordinator:でアニメーション処理を記述する。NCWidgetDisplayMode
が変更され、高さが変更される際にこのメソッドが呼び出されるので、引数のcoordinator
のanimate(alongsideTransition:completion:)を使ってアニメーションの処理を行う。以下のサンプルは、透過度を変更している。
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animate(alongsideTransition: { [weak self] context in
self?.textLabel.alpha = 0.3
self?.imageView.alpha = 0.3
}, completion: { [weak self] _ in
self?.textLabel.alpha = 1
self?.imageView.alpha = 1
})
}
Widgets
の更新
システムが、Widgets
の内容を更新すべきタイミングで、widgetPerformUpdateWithCompletionHandler:を呼び出す。通知センターが表示されている時や、バックグラウンドでも呼び出される。Widgets
は定期的にスナップショットが取られていて、View
が更新されるまでは最新のスナップショットが表示されるようになっている。
public enum NCUpdateResult : UInt {
//最新の情報を表示したい場合
case newData
//最新状態から変更がなく、更新する必要がない場合
case noData
//アップデートに失敗した際
case failed
}
更新のステータスは、引数のクロージャに対してNCUpdateResultを返すようになっている。
func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
fetchTodayData() { data, error in
if let _ = error {
completionHandler(.failed)
} else {
updateUI(data)
completionHandler(.newData)
}
}
}
Tapイベントやアプリ起動
通常のアプリ同様UIのタップイベントをキャプチャすることができる。通常のプロジェクト同様MainInterface.storyboard
とTodayViewController
を関連づけて設定する。
@IBAction func tappedButton(_ sender: Any) {
let url = URL(string: "todaySample://")
extensionContext?.open(url!, completionHandler: nil)
}
また、extensionContext
を使用することで、Widgets
からアプリを開くこともできる。
URL schemes
は、Show the Project navigator > TARGETS > info > URL Types
より設定する。
Widgets
の表示/非表示
アプリ側からWidgets
の表示/非表示を設定することができる。
setHasContent(_:forWidgetWithBundleIdentifier:)のflag
を設定する。(forWidgetWithBundleIdentifier
はextensionのBundle Identifier
を設定する。)
import NotificationCenter
let widgetController = NCWidgetController()
widgetController.setHasContent(flg, forWidgetWithBundleIdentifier: "com.Today.TodayExtension")
注意点やガイドライン
-
Widgets
は縦横のスクロールが可能なので、ScrollView
の使用は非推奨 - 操作はシンプルなものにする(タスクはシングルタップで完結させる)
- キーボードは使用できない
-
Widgets
の高さの上限は、端末の高さである -
Widgets
は自動でPaddingが設定される -
Widgets
の背景色の設定は非推奨 - テキストの色は読みやすい色にする。黒か暗い灰色が推奨されている