iOSのWidgetについて
iOS10からホームのアイコンをプレスしてWidgetが表示されるようになったので少し調べてみました。
途中3D Touchに関する記述がありますが、iOS 3D Touchを参照していただけると幸いです。
何か指摘して頂けるとすぐ対応したいと思います。
おそらく後から変更することがあると思います。
WidgetはiOS8から実装できるようになり、iOS10から少し機能が増えました。
ウィジェットは少量ですが、アプリの固有の情報を表示する拡張機能です。
ロック画面でもウィジェットの表示を確認する為には、設定 -> Touch IDとパスコード -> 今日の表示をONで表示を確認することができると思います。
(左)検索画面のウィジェット | (右)ホーム画面のクイックアクションによるウィジェット
iOS8からの機能として検索画面のウィジェットが追加され、iOS9から追加された3D Touchを使って iOS10からWidgetを表示できるようになりました。
ウィジェットの実装方法
まずプロジェクトにToday Extensionを追加する必要があります。
なので、[File] -> [New] -> [Target] -> [Today Extension]を選びましょう。
初期設定として、Product Nameがウィジェットのタイトルとなりますが後で変更することが可能です。
これだけでホームのアイコンをプレスするとウィジェットが表示されると思います。
今回、TodayとProduct Nameをつけたので
Todayと表示されます。(Hello Worldはデフォルトです)
ここまでの時点でTodayディレクトリの配下にはデフォルトで
・TodayViewController.swift
・MainInterface.storyboard
・Info.plist
の3つのファイルが生成されていると思います。
##各ファイル主な説明
TodayViewControllerについて
widgetPerformUpdateは新しい更新をウィジェットで表示するためのメソッドです。
widgetPerformUpdateのcompletionHandlerにNCUpdateResultがありますが、これはNCWidgetProvidingプロトコルの定数が返ってきます。
typedef NS_ENUM(NSUInteger, NCUpdateResult) {
NCUpdateResultNewData,
NCUpdateResultNoData,
NCUpdateResultFailed
} NS_ENUM_AVAILABLE_IOS(8_0);
このように定義されており、この上記3つを使って状態をwidgetPerformUpdateメソッドの中で管理していきます。
もう既にTodayViewController内でコメントアウトされている
func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
// Perform any setup necessary in order to update the view.
// If an error is encountered, use NCUpdateResult.Failed
// If there's no update required, use NCUpdateResult.NoData
// If there's an update, use NCUpdateResult.NewData
completionHandler(NCUpdateResult.newData)
}
上記の通りで、
NCUpdateResult.Failed : アップデートに失敗した時
NCUpdateResult.NoData : 最後の更新から何も変化がなかった時
NCUpdateResult.NewData : 新しいデータが更新され、レイアウトを更新する必要がある時
に返ってくるので、これらを使って管理します。
MainInterface.storyboardについて
ウィジェットとして表示される。
デフォルトでHelloWorldが表示されるようになっています。
もちろんカスタム可能。
Info.plistについて
CFBundleDisplayNameを変更すると、ウィジェットで表示される名前が変更されます。
<key>NSExtension</key>
<dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widget-extension</string>
</dict>
NSExtensionMainStoryboardでMainInterfaceが設定されているので、MainInterface.storyboardが呼ばれます。
なので、NSExtensionMainStoryboardを変更することでカスタム可能です。
##ウィジェットをタップしてアプリを起動させる方法
ウィジェットでタップを検知できるようにさせましょう。(ここは省略)
TodayViewControllerに
@IBAction func tappedLabel(_ sender: UILabel) {
}
としてHello Worldのラベルをタップ検知できるようにしました。
これだけではタップイベントしか取得できないので、アプリを開かせる為にCFBundleURLSchemesを変更します。
プロジェクトのターゲットでInfoの一番下URL Typesに1つ追加しました。
今回は適当にhelloとしましたが、これは他のアプリと被らないように設定してあげましょう。
そうしたらTodayViewController.swiftに行き、
extensionContextを使って
@IBAction func tappedLabel(_ sender: UILabel) {
guard let url = URL(string: "hello://") else { return }
extensionContext?.open(url, completionHandler: { (success: Bool) in })
}
とするとウィジェットのHello Worldラベルをタップした時にアプリを起動させることができるようになったと思います。
このCFBundleURLSchemesにMusicとするとMusicアプリが起動される通り、ユニークキーにする必要があります。
因みに、TodayViewController.swiftでブレークポイントやprint文が反映されないのは
選択されているScheme違いで、Today Extensionで追加したスキームを選択してください。
print文が反映されるかと思います。
Today Extensionのガイドライン
-
ユーザーが今必要な情報に素早くアクセスできる。
- ユーザーは頻繁にウィジェットから開く傾向があり、彼らはすぐに利用できることを期待している。
- ユーザーがウィジェットの内容や動作を設定する為にアプリを使用できるようにする必要があります。
- Today Extensionを使用する場合、デフォルトで実装ファイルのTodayViewControllerとInfo.plist、ストーリボードまたはXibが作成されます。
-
UIの設計
- Auto Layoutで設計してください。
- 大きいウィジェットを作成しないでください。
多くのコンテンツを表示する為に高さを調節することはできます。 - ウィジェットは自動でインセットのレイアウトの制約がかかりますがwidgetMarginInsetsForProposedMarginInsets:で値を受け取ったり、カスタムのインセットを返すこともできる。
-> iOS10から非推奨(iOS10では呼び出されない。) - テンプレートの主なコントローラはNCWidgetProvidingに準拠されているので、これらを使ってウィジェットのコンテンツを描画してください。
- ウィジェットが追加コンテンツを表示する場合、適切なウィジェットの高さを調節する自動レイアウトの制約に依存することができる。
- 自動レイアウトさせたくない場合は、UIViewControllerのpreferredContentSizeで高さを調節してください。
※スクロールするユーザーの為にウィジェットの高さを指定しないでください。 - コンテンツの表示をアニメーションしたい場合は、viewWillTransitionToSize:withTransitionCoordinator:を使用し、viewWillTransitionToSize:withTransitionCoordinator:のcoordinatorパラメーターを調節しましょう。
-
更新コンテンツ
- ウィジェットで最新の状態を表示する為のスナップショットを取得できます。 NCWidgetProvidingプロトコルが提供するwidgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void))メソッドに処理を記述します。
※ Xcode8.0でコンパイル時に警告が出ましたが、上記のように@escapingをつけることで解決しました。Xcode8のリリースノート
-
ウィジェットが表示されるときに指定
-
setHasContent:forWidgetWithBundleIdentifier:をつかってウィジェットのコンテンツの表示、非表示を切り替えることができる。
このメソッドはPush通知をトリガーにすることもできる。 - Today ExtensionではopenURL:completionHandler:メソッドを使うことができ、これを使ってアプリを開くことができる。
ウィジェットのiOSのヒューマンインターフェイスガイドライン
-
一目で分かる設計にすること
- 可能な限り、シングルタップで完了できるタスクを提供すること。
- ドラッグやスクロールなどをさせるのはやめましょう。
- データを取得するようなものはローカルにキャッシュさせて表示させること。
- ウィジェットの縁まわりを拡張しないでください。
- ウィジェットをグリッドで表示させるような場合は、十分かつ平等なパディングを用意してください。
可能な場合、一行あたり4つまでのアイコンに制限してください。 - ウィジェットの幅はデバイスによって異なります。
- Quick Actionによるウィジェットの高さは、二行半です。
- 人によってiPhoneの背景は違うので、見づらい場合が発生することもあるから。
- 読みやすいから。
- 標準のウィジェットの背景とうまくマッチする。
- ウィジェット自体はアプリから独立して動作するべきですが、表示されている情報以上のことを提供する必要がある場合もある。
しかし、アプリを開くためのボタンなどを用意するのはやめましょう。
ユーザーはコンテンツをタップして確認するでしょう。 - 他のアプリを開くためのウィジェットを作成しないでください。
- アプリのアイコンとタイトルが各ウィジェットのコンテンツの上に表示されます。
- 一般的には、ウィジェットの名前は、アプリの名前と一致する必要があります。
- 複数のウィジェットを提供しているならば、アプリ名を使用するようにしてください。
- カスタムなタイトルをつける場合、アプリ名を先頭につけるよう検討してください。
例えばマップアプリでは"マップ: 目的地"というようになっています。 - ウィジェットにアプリでログインしている必要がある場合、ユーザーにそのことを知らせましょう。
例えば、予約情報をウィジェットで表示するのにログインしている必要がある時は、「あなたの予約を表示するには、アプリにログインする必要があります」と明記する。 - ユーザーが3D Touchを使用して、アプリアイコンに圧力を加えたときクイックアクションメニューに表示するものを選びます。
App Extensionsプログラミングガイド
実はほぼ全て載っていた。