iOSアプリにWidgetを追加しよう

# iOSのWidgetについて
iOS10からホームのアイコンをプレスしてWidgetが表示されるようになったので少し調べてみました。
途中3D Touchに関する記述がありますが、[iOS 3D Touch](http://qiita.com/masahiro_kusumoto/items/f33c89f150cd0937d003)を参照していただけると幸いです。

何か指摘して頂けるとすぐ対応したいと思います。
おそらく後から変更することがあると思います。

WidgetはiOS8から実装できるようになり、iOS10から少し機能が増えました。
ウィジェットは少量ですが、アプリの固有の情報を表示する拡張機能です。
ロック画面でもウィジェットの表示を確認する為には、設定 -> Touch IDとパスコード -> 今日の表示をONで表示を確認することができると思います。

(左)検索画面のウィジェット | (右)ホーム画面のクイックアクションによるウィジェット
---- 
<img src="https://developer.apple.com/ios/human-interface-guidelines/images/Widget_Search_2x.png" width="300"/>   <img src="https://developer.apple.com/ios/human-interface-guidelines/images/Widget_Stock_2x.png" width="300"/> 
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](https://developer.apple.com/reference/notificationcenter/ncwidgetproviding/1490262-widgetperformupdate)は新しい更新をウィジェットで表示するためのメソッドです。
[widgetPerformUpdate](https://developer.apple.com/reference/notificationcenter/ncwidgetproviding/1490262-widgetperformupdate)のcompletionHandlerに[NCUpdateResult](https://developer.apple.com/reference/notificationcenter/ncupdateresult)がありますが、これは[NCWidgetProviding](https://developer.apple.com/reference/notificationcenter/ncwidgetproviding)プロトコルの定数が返ってきます。

```.swift
typedef NS_ENUM(NSUInteger, NCUpdateResult) {
    NCUpdateResultNewData,
    NCUpdateResultNoData,
    NCUpdateResultFailed
} NS_ENUM_AVAILABLE_IOS(8_0);
```
このように定義されており、この上記3つを使って状態を[widgetPerformUpdate](https://developer.apple.com/reference/notificationcenter/ncwidgetproviding/1490262-widgetperformupdate)メソッドの中で管理していきます。
もう既にTodayViewController内でコメントアウトされている

```.swift
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に

```.swift
@IBAction func tappedLabel(_ sender: UILabel) {
}
```

としてHello Worldのラベルをタップ検知できるようにしました。

これだけではタップイベントしか取得できないので、アプリを開かせる為にCFBundleURLSchemesを変更します。

プロジェクトのターゲットでInfoの一番下URL Typesに1つ追加しました。
今回は適当にhelloとしましたが、これは他のアプリと被らないように設定してあげましょう。

そうしたらTodayViewController.swiftに行き、
[extensionContext](https://developer.apple.com/reference/uikit/uiviewcontroller/1621411-extensioncontext)を使って

```.swift
@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のガイドライン](https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/Today.html)
<ul>
  <strong>ユーザーが今必要な情報に素早くアクセスできる。</strong>
  <ul>
    <li>ユーザーは頻繁にウィジェットから開く傾向があり、彼らはすぐに利用できることを期待している。</li>
  </ul>
  <strong>ウィジェットはキーボードをサポートしていない。</strong>
  <ul>
    <li>ユーザーがウィジェットの内容や動作を設定する為にアプリを使用できるようにする必要があります。</li>
  </ul>
  <strong>XcodeのToday Templateを使用してください。</strong>
  <ul>
    <li>Today Extensionを使用する場合、デフォルトで実装ファイルのTodayViewControllerとInfo.plist、ストーリボードまたはXibが作成されます。</li>
  </ul>
</ul>
```
<key>NSExtension</key>
    <dict>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.widget-extension</string>
        <key>NSExtensionPrincipalClass</key>
        <string>TodayViewController</string>
    </dict>
```
ストーリーボードを使わない場合(カスタムなViewControllerを使う場合)は、変更する為にNSExtensionMainStoryboardを削除してNSExtensionPrincipalClassを追加してください。
このキーにウィジェットとして追加するViewControllerクラスを指定します。

<ul>
  <strong>UIの設計</strong>
  <ul>
    <li>Auto Layoutで設計してください。</li>
    <li>大きいウィジェットを作成しないでください。<br/>多くのコンテンツを表示する為に高さを調節することはできます。</li>
    <li>ウィジェットは自動でインセットのレイアウトの制約がかかりますが<a href="https://developer.apple.com/reference/notificationcenter/ncwidgetproviding/1490248-widgetmargininsets">widgetMarginInsetsForProposedMarginInsets:</a>で値を受け取ったり、カスタムのインセットを返すこともできる。<br/>-> iOS10から非推奨(iOS10では呼び出されない。)</li>
    <li>テンプレートの主なコントローラは<a href="https://developer.apple.com/reference/notificationcenter/ncwidgetproviding">NCWidgetProviding</a>に準拠されているので、これらを使ってウィジェットのコンテンツを描画してください。</li>
    <li>ウィジェットが追加コンテンツを表示する場合、適切なウィジェットの高さを調節する自動レイアウトの制約に依存することができる。</li>
    <li>自動レイアウトさせたくない場合は、UIViewControllerの<a href="https://developer.apple.com/reference/uikit/uiviewcontroller/1621476-preferredcontentsize">preferredContentSize</a>で高さを調節してください。<br/>※スクロールするユーザーの為にウィジェットの高さを指定しないでください。</li>
  </ul>
  <ul>
    <li>コンテンツの表示をアニメーションしたい場合は、viewWillTransitionToSize:withTransitionCoordinator:を使用し、<a href="https://developer.apple.com/reference/uikit/uiviewcontrollertransitioncoordinator/1619300-animate">viewWillTransitionToSize:withTransitionCoordinator:</a>のcoordinatorパラメーターを調節しましょう。</li>
  </ul>
</ul>

<ul>
  <strong>更新コンテンツ</strong>
  <ul>
    <li>ウィジェットで最新の状態を表示する為のスナップショットを取得できます。</li>
    <a href="https://developer.apple.com/reference/notificationcenter/ncwidgetproviding">NCWidgetProviding</a>プロトコルが提供するwidgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void))メソッドに処理を記述します。<br/>
    ※ Xcode8.0でコンパイル時に警告が出ましたが、上記のように<a href="https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.htmlg">@escaping</a>をつけることで解決しました。<a href="https://developer.apple.com/library/content/releasenotes/DeveloperTools/RN-Xcode/Introduction.html">Xcode8のリリースノート</a>
  </ul>
</ul>
        NCUpdateResultFailed - 更新失敗
        NCUpdateResultNoData - 更新されていない
        NCUpdateResultNewData - 更新成功

<ul>
  <strong>ウィジェットが表示されるときに指定</strong>
  <ul>
    <li><a href="https://developer.apple.com/reference/notificationcenter/ncwidgetcontroller/1456693-sethascontent">setHasContent:forWidgetWithBundleIdentifier:</a>をつかってウィジェットのコンテンツの表示、非表示を切り替えることができる。<br/>このメソッドはPush通知をトリガーにすることもできる。</li>
  </ul>
  <strong>アプリを開く</strong>
  <ul>
    <li>Today Extensionでは<a href="https://developer.apple.com/reference/foundation/nsextensioncontext/1416791-openurl">openURL:completionHandler:</a>メソッドを使うことができ、これを使ってアプリを開くことができる。</li>
  </ul>
</ul>

# [ウィジェットのiOSのヒューマンインターフェイスガイドライン](https://developer.apple.com/ios/human-interface-guidelines/extensions/widgets/)
<ul>
  <strong>一目で分かる設計にすること</strong>
    <ul>
      <li>可能な限り、シングルタップで完了できるタスクを提供すること。</li>
      <li>ドラッグやスクロールなどをさせるのはやめましょう。</li>
    </ul>
  <strong>すぐにコンテンツを表示させる</strong>
    <ul>
      <li>データを取得するようなものはローカルにキャッシュさせて表示させること。</li>
    </ul>
  <strong>十分なマージンとパディングを設けること</strong>
    <ul>
      <li>ウィジェットの縁まわりを拡張しないでください。</li>
      <li>ウィジェットをグリッドで表示させるような場合は、十分かつ平等なパディングを用意してください。
      <br>可能な場合、一行あたり4つまでのアイコンに制限してください。</li>
    </ul>
  <strong>デバイスの向きに適応できるようにすること</strong>
    <ul>
      <li>ウィジェットの幅はデバイスによって異なります。</li>
      <li>Quick Actionによるウィジェットの高さは、二行半です。</li>
    </ul>
  <strong>Widgetの背景はカスタマイズしないこと</strong>
    <ul>
      <li>人によってiPhoneの背景は違うので、見づらい場合が発生することもあるから。</li>
    </ul>
  <strong>テキストの色は黒か暗い灰色に</strong>
    <ul>
      <li>読みやすいから。</li>
      <li>標準のウィジェットの背景とうまくマッチする。</li>
    </ul>
  <strong>適切な場合、タップで画面にジャンプできるようにしよう</strong>
    <ul>
      <li>ウィジェット自体はアプリから独立して動作するべきですが、表示されている情報以上のことを提供する必要がある場合もある。<br/>しかし、アプリを開くためのボタンなどを用意するのはやめましょう。<br/>ユーザーはコンテンツをタップして確認するでしょう。</li>
      <li>他のアプリを開くためのウィジェットを作成しないでください。</li>
    </ul>
  <strong>最適なウィジェット名にしてください。</strong>
    <ul>
      <li>アプリのアイコンとタイトルが各ウィジェットのコンテンツの上に表示されます。</li>
      <li>一般的には、ウィジェットの名前は、アプリの名前と一致する必要があります。</li>
      <li>複数のウィジェットを提供しているならば、アプリ名を使用するようにしてください。</li>
      <li>カスタムなタイトルをつける場合、アプリ名を先頭につけるよう検討してください。<br/>例えばマップアプリでは"マップ: 目的地"というようになっています。</li>
    </ul>
  <strong>認証情報が欲しい場合はユーザーに知らせよう</strong>
    <ul>
      <li>ウィジェットにアプリでログインしている必要がある場合、ユーザーにそのことを知らせましょう。<br/>例えば、予約情報をウィジェットで表示するのにログインしている必要がある時は、「あなたの予約を表示するには、アプリにログインする必要があります」と明記する。</li>
    </ul>
  <strong>Quick Actionにウィジェットを</strong>
    <ul>
      <li>ユーザーが3D Touchを使用して、アプリアイコンに圧力を加えたときクイックアクションメニューに表示するものを選びます。</li>
    </ul>
</ul>

#### [App Extensionsプログラミングガイド](https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/Today.html)

実はほぼ全て載っていた。