post Image
Widgets(Today Extension)のまとめ

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のボタンが追加され、拡大時の高さを変えられるようになる。(幅は変えられない)

NCWidgetTypes.h
@available(iOS 10.0, *)
public enum NCWidgetDisplayMode : Int {


    case compact // Fixed height

    case expanded // Variable height
}
TodayViewController

// 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が変更され、高さが変更される際にこのメソッドが呼び出されるので、引数のcoordinatoranimate(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を返すようになっている。

TodayViewController.swift
func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {

        fetchTodayData() { data, error in
            if let _ = error { 
                completionHandler(.failed) 
            } else {
                updateUI(data)
                completionHandler(.newData)
            }
        }
}

Tapイベントやアプリ起動

通常のアプリ同様UIのタップイベントをキャプチャすることができる。通常のプロジェクト同様MainInterface.storyboardTodayViewControllerを関連づけて設定する。

@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の背景色の設定は非推奨
  • テキストの色は読みやすい色にする。黒か暗い灰色が推奨されている

サンプルコード

shoheiyokoyama / Today

参考


『 Swift 』Article List