Today Extension
App Extension TypeであるTodayは、通知センターにビューに置いて表示内容を即座に更新したり、簡単なタスクを実行することができWidgetsとも呼ばれる。その日の株価や天気、スケジュールやタスクなどを簡単に示し、ユーザが簡単に情報を確認することができる。
導入
Extension用のTarget作成する(File > New > Target > Today Extension)
作成したExtensionのディレクトリ配下に3つのファイルが作成される。
TodayViewController.swiftMainInterface.storyboardInfo.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の背景色の設定は非推奨 - テキストの色は読みやすい色にする。黒か暗い灰色が推奨されている


