Xamarin.iOS での iOS 拡張機能
iOS での拡張機能の作成のビデオ
iOS 8 で導入された拡張機能は、通知センター内などの標準コンテキスト内で、特殊な入力や他のコンテキスト (拡張機能で特殊な効果フィルターを提供できる写真編集など) を実行するためにユーザーが要求したカスタム キーボードの種類として iOS によって提示される特殊な UIViewControllers
です。
すべての拡張機能は、コンテナー アプリと組み合わせてインストールされ (どちらの要素も 64 ビット Unified API を使って記述されているもの)、ホスト アプリの特定の拡張ポイントからアクティブ化されます。 また、それらは既存のシステム機能を補完するものとして使われるため、高性能で無駄がなく堅牢である必要があります。
拡張ポイント
型 | 説明 | 拡張ポイント | ホスト アプリ |
---|---|---|---|
アクション | 特定のメディアの種類に特化したエディターまたはビューアー | com.apple.ui-services |
任意 |
ドキュメント プロバイダー | アプリでリモート ドキュメント ストアを使用できるようにする | com.apple.fileprovider-ui |
UIDocumentPickerViewController を使用するアプリ |
[キーボード] | 代替キーボード | com.apple.keyboard-service |
任意 |
写真の編集 | 写真の操作と編集 | com.apple.photo-editing |
Photos.app エディター |
共有 | ソーシャル ネットワーク、メッセージング サービスなどとデータを共有する | com.apple.share-services |
任意 |
今日 | Today 画面または通知センターに表示される "ウィジェット" | com.apple.widget-extensions |
Today と通知センター |
追加の拡張ポイントが、iOS 10 と iOS 12 で追加されました。 サポートされているすべての種類の完全な表については、iOS のアプリ拡張機能プログラミング ガイドに関するページをご覧ください。
制限事項
拡張機能にはさまざまな制限があり、その一部はすべての種類に共通です (たとえば、どの拡張機能の種類もカメラやマイクにアクセスできません)。一方、他の拡張機能の種類に固有の使用制限がある場合があります (たとえば、パスワードなどのセキュリティ保護されたデータ入力フィールドには、カスタム キーボードを使用できません)。
全体に対する制限は次のとおりです。
- Health Kit と Event Kit UI フレームワークは使用できません
- 拡張機能では、拡張バックグラウンド モードを使用できません
- 拡張機能は、デバイスのカメラまたはマイクにアクセスできません (ただし、既存のメディア ファイルにはアクセスできる場合があります)
- 拡張機能は、AirDrop データを受信できません (ただし、AirDrop 経由でデータを送信することはできます)
- UIActionSheet と UIAlertView は使用できません。拡張機能では、UIAlertController を使う必要があります
- UIApplication のいくつかのメンバーを使用できません: UIApplication.SharedApplication、UIApplication.OpenUrl、UIApplication.BeginIgnoringInteractionEvents、UIApplication.EndIgnoringInteractionEvents
- iOS では、Today の拡張機能に 16 MB のメモリ使用制限が適用されます。
- 既定では、キーボード拡張機能はネットワークにアクセスできません。 Xamarin.iOS でデバッグが機能するにはネットワーク アクセスが必要であるため、これはデバイスでのデバッグに影響します (シミュレーターでは制限は適用されません)。 プロジェクトの Info.plist で
Requests Open Access
の値をYes
に設定して、ネットワーク アクセスを要求できます。 キーボード拡張機能の制限について詳しくは、Apple のカスタム キーボード ガイドに関するページをご覧ください。
個々の制限については、Apple の「アプリ拡張機能プログラミング ガイド」をご覧ください。
拡張機能の配布、インストール、実行
拡張機能は、コンテナー アプリ内から配布され、そこからさらに App Store 経由で送信および配布されます。 アプリと共に配布された拡張機能は、その時点でインストールされますが、ユーザーは各拡張機能を明示的に有効にする必要があります。 さまざまな種類の拡張機能が、さまざまな方法で有効にされます。その一部では、ユーザーが設定アプリに移動して、そこから有効にする必要があります。 それ以外は、写真送信時の共有拡張機能の有効化など、使われる時点で有効になります。
拡張機能が使われるアプリ (ユーザーが拡張ポイントを検出する場所) は、拡張機能の実行時にそれをホストするアプリであるため、ホスト アプリと呼ばれます。 拡張機能をインストールするアプリは、インストール時に拡張機能が含まれるアプリであるため、コンテナー アプリです。
通常、コンテナー アプリでは拡張機能について説明し、それを有効にするプロセスをユーザーに示します。
拡張機能のデバッグ バージョンとリリース バージョン
アプリ拡張機能の実行に対するメモリ制限は、フォアグラウンド アプリに適用されるメモリ制限より大幅に低くなります。 iOS を実行するシミュレーターでは、拡張機能に適用される制限が少なくなり、問題なく拡張機能を実行できます。 ただし、同じ拡張機能をデバイスで実行すると、拡張機能がクラッシュしたり、システムによって強制的に終了されたりするなど、予期しない結果になる可能性があります。 そのため、配布する前に、デバイスで拡張機能をビルドしてテストしてください。
コンテナー プロジェクトと参照されているすべての拡張機能に、次の設定が適用されていることを確認する必要があります。
- リリース構成でアプリケーション パッケージをビルドします。
- [iOS ビルド] プロジェクトの設定で、[リンカーの動作] オプションを [フレームワーク SDK のみをリンクする] または [すべてをリンクする] に設定します。
- [iOS Debug] プロジェクトの設定で、[デバッグ を有効にする] と [プロファイルを有効にする] オプションをオフにします。
拡張機能のライフサイクル
拡張機能は、1 つの UIViewController 程度に単純にすることも、複数の UI 画面を表示するさらに複雑な拡張機能にすることもできます。 ユーザーは、"拡張ポイント" (画像の共有時など) にアクセスしたら、その拡張ポイントに登録されている拡張機能から選択できます。
ユーザーがアプリの拡張機能の 1 つを選ぶと、その UIViewController
がインスタンス化され、通常のビュー コントローラーライフサイクルが開始されます。 ただし、ユーザーが操作を終了しても一時停止されるだけで一般には終了されない通常のアプリとは異なり、拡張機能は読み込み、実行、終了が繰り返されます。
拡張機能は、NSExtensionContext オブジェクトを介してホスト アプリと通信できます。 一部の拡張機能には、結果で非同期コールバックを受け取る操作があります。 これらのコールバックはバックグラウンド スレッドで実行され、拡張機能ではこのことを考慮する必要があります。たとえば、ユーザー インターフェイスを更新したい場合は、NSObject.InvokeOnMainThread を使います。 詳しくは、後の「ホスト アプリとの通信」セクションをご覧ください。
既定では、拡張機能とそのコンテナー アプリは、一緒にインストールされても通信できません。 コンテナー アプリは、拡張機能がインストールされるとその目的が果たされる、基本的に空の "配布" コンテナーである場合があります。 ただし、状況によっては、コンテナー アプリと拡張機能が共通領域のリソースを共有する場合があります。 さらに、Today 拡張機能は、そのコンテナー アプリに URL を開くよう要求する場合があります。 この動作は、イベント カウントダウン ウィジェットで示されています。
拡張機能の作成
拡張機能 (およびそのコンテナー アプリ) は、64 ビット バイナリでなければならず、Xamarin.iOS Unified API を使ってビルドされている必要があります。 拡張機能を開発するときは、ソリューションに少なくとも 2 つのプロジェクト (コンテナー アプリと、コンテナーが提供する拡張機能ごとに 1 つのプロジェクト) が含まれます。
コンテナー アプリ プロジェクトの要件
拡張機能のインストールに使われるコンテナー アプリには、次の要件があります。
- 拡張機能プロジェクトへの参照を保持する必要があります。
- 拡張機能をインストールする手段を提供する以外に何も行わない場合でも、完全なアプリである必要があります (正常に起動して実行できる必要があります)。
- 拡張機能プロジェクトのバンドル識別子の基になるバンドル識別子を持っている必要があります (詳しくは、以下のセクションをご覧ください)。
拡張機能プロジェクトの要件
さらに、拡張機能プロジェクトには次の要件があります。
コンテナー アプリのバンドル識別子で始まるバンドル識別子が必要です。 たとえば、コンテナー アプリのバンドル識別子が
com.myCompany.ContainerApp
である場合、拡張機能の識別子はcom.myCompany.ContainerApp.MyExtension
などになります。Info.plist
ファイルで、キーNSExtensionPointIdentifier
と適切な値 (Today 通知センター ウィジェットの場合はcom.apple.widget-extension
など) が定義されている必要があります。また、
Info.plist
ファイルでは、NSExtensionMainStoryboard
キーまたはNSExtensionPrincipalClass
キーの "いずれか" と、適切な値が定義されている必要もあります。NSExtensionMainStoryboard
キーを使って、拡張機能のメイン UI を表示するストーリーボードの名前を指定します (.storyboard
を除く)。 たとえば、Main.storyboard
ファイルの場合はMain
です。NSExtensionPrincipalClass
キーを使って、拡張機能の開始時に初期化されるクラスを指定します。 その値は、UIViewController
の Register の値と一致している必要があります。
特定の種類の拡張機能には、追加の要件がある場合があります。 たとえば、Today または通知センター拡張機能のプリンシパル クラスでは、INCWidgetProviding を実装する必要があります。
重要
Visual Studio for Mac で提供されている拡張機能テンプレートのいずれかを使ってプロジェクトを開始すると、これらの要件の (すべてではないにしても) ほとんどがテンプレートによって自動的に提供されて、満たされます。
チュートリアル
次のチュートリアルでは、年の何日目かと残りの日数を計算する Today ウィジェットの例を作成します。
ソリューションの作成
必要なソリューションを作成するには、次のようにします。
最初に、新しい iOS の [単一ビュー アプリ] プロジェクトを作成して、[次へ] ボタンをクリックします。
プロジェクトに
TodayContainer
という名前を付けて、[次へ] ボタンをクリックします。[プロジェクト名] と [ソリューション名] を確認し、[作成] ボタンをクリックしてソリューションを作成します。
次に、ソリューション エクスプローラーでソリューションを右クリックし、Today Extension テンプレートから新しい iOS 拡張機能プロジェクトを追加します。
プロジェクトに
DaysRemaining
という名前を付けて、[次へ] ボタンをクリックします。プロジェクトを確認し、[作成] ボタンをクリックして作成します。
次に示すように、結果のソリューションには 2 つのプロジェクトが含まれているはずです。
拡張機能のユーザー インターフェイスの作成
次に、Today ウィジェットのインターフェイスを設計する必要があります。 これは、ストーリーボードを使うか、コードで UI を作成して、行うことができます。 以下では両方の方法について詳しく説明します。
ストーリーボードの使用
ストーリーボードを使って UI を作成するには、次のようにします。
ソリューション エクスプローラーで、拡張機能プロジェクトの
Main.storyboard
ファイルをダブルクリックして編集用に開きます。テンプレートによって UI に自動的に追加されたラベルを選択し、Properties エクスプローラーの Widget タブで
TodayMessage
Name を指定します:ストーリーボードへの変更を保存します。
コードの使用
コードで UI を作成するには、次のようにします。
ソリューション エクスプローラーで DaysRemaining プロジェクトを選び、新しいクラスを追加して、名前を
CodeBasedViewController
にします。再び、ソリューション エクスプローラーで拡張機能の
Info.plist
ファイルをダブルクリックして、編集用に開きます。(画面の下部で) [ソース ビュー] を選んで、
NSExtension
ノードを開きます。NSExtensionMainStoryboard
キーを削除し、値をCodeBasedViewController
にしてNSExtensionPrincipalClass
を追加します。変更を保存。
次に、CodeBasedViewController.cs
ファイルを編集して、次のようにします。
using System;
using Foundation;
using UIKit;
using NotificationCenter;
using CoreGraphics;
namespace DaysRemaining
{
[Register("CodeBasedViewController")]
public class CodeBasedViewController : UIViewController, INCWidgetProviding
{
public CodeBasedViewController ()
{
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Add label to view
var TodayMessage = new UILabel (new CGRect (0, 0, View.Frame.Width, View.Frame.Height)) {
TextAlignment = UITextAlignment.Center
};
View.AddSubview (TodayMessage);
// Insert code to power extension here...
}
}
}
[Register("CodeBasedViewController")]
が、上で NSExtensionPrincipalClass
に指定した値と一致することに注意してください。
拡張機能のコーディング
ユーザー インターフェイスを作成したら、(上でユーザー インターフェイスの作成に使った方法に基づいて) TodayViewController.cs
または CodeBasedViewController.cs
ファイルを開き、ViewDidLoad メソッドを次のように変更します。
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Calculate the values
var dayOfYear = DateTime.Now.DayOfYear;
var leapYearExtra = DateTime.IsLeapYear (DateTime.Now.Year) ? 1 : 0;
var daysRemaining = 365 + leapYearExtra - dayOfYear;
// Display the message
if (daysRemaining == 1) {
TodayMessage.Text = String.Format ("Today is day {0}. There is one day remaining in the year.", dayOfYear);
} else {
TodayMessage.Text = String.Format ("Today is day {0}. There are {1} days remaining in the year.", dayOfYear, daysRemaining);
}
}
コード ベースのユーザー インターフェイスの方法を使っている場合は、// Insert code to power extension here...
というコメントを上の新しいコードに置き換えます。 基本の実装の呼び出し (および、コード ベースのバージョンの場合はラベルの挿入) を行った後、このコードは簡単な計算を行って、年の通算日数と残りの日数を取得します。 次に、UI デザインで作成したラベル (TodayMessage
) にメッセージを表示します。
このプロセスが、アプリを記述する通常のプロセスとどのように似ているかに注意してください。 拡張機能の UIViewController
のライフサイクルは、アプリのビュー コントローラーと同じですが、拡張機能にはバックグラウンド モードがなく、ユーザーがそれを使い終わっても一時停止されない点が異なります。 代わりに、拡張機能は必要に応じて繰り返し初期化されて割り当て解除されます。
コンテナー アプリのユーザー インターフェイスの作成
このチュートリアルのコンテナー アプリは、拡張機能を配布およびインストールするための方法としてのみ使われており、独自の機能は提供していません。 TodayContainer の Main.storyboard
ファイルを編集し、拡張機能の機能とそのインストール方法を定義するテキストを追加します。
ストーリーボードへの変更を保存します。
拡張機能のテスト
iOS シミュレーターで拡張機能をテストするには、TodayContainer アプリを実行します。 コンテナーのメイン ビューが表示されます。
次に、シミュレーターの [ホーム] ボタンを選び、画面の上から下にスワイプして通知センターを開き、[Today] タブを選んで、[編集] ボタンをクリックします。
DaysRemaining 拡張機能を [Today] ビューに追加して、[完了] ボタンをクリックします。
新しいウィジェットが [Today] ビューに追加されて、結果が表示されます。
ホスト アプリとの通信
上で作成した Today 拡張機能の例は、そのホスト アプリ ([Today] 画面) と通信しません。 もし行っていた場合は、TodayViewController
または CodeBasedViewController
クラスの ExtensionContext プロパティを使います。
ホスト アプリからデータを受け取る拡張機能の場合、データは、拡張機能の UIViewController
の ExtensionContext の InputItems プロパティに格納されている NSExtensionItem オブジェクトの配列の形式になります。
写真編集拡張機能などの他の拡張機能では、ユーザーによる完了またはキャンセルの使用を区別できます。 これは、ExtensionContext プロパティの CompleteRequest と CancelRequest メソッドを介してホスト アプリに通知されます。
詳しくは、Apple の「アプリ拡張機能プログラミング ガイド」をご覧ください。
親アプリとの通信
アプリ グループを使用すると、さまざまなアプリケーション (またはアプリケーションとその拡張機能) から共有ファイルの保存場所にアクセスできます。 アプリ グループは次のようなデータに使用できます。
詳しくは、機能の使用に関するドキュメントのアプリ グループに関するセクションをご覧ください。
MobileCoreServices
拡張機能を使用する場合は、Uniform Type Identifier (UTI) を使って、アプリ、他のアプリ、サービスの間で交換されるデータを作成して操作します。
MobileCoreServices.UTType
静的クラスでは、Apple の kUTType...
定義に関連する次のヘルパー プロパティを定義します。
kUTTypeAlembic
-Alembic
kUTTypeAliasFile
-AliasFile
kUTTypeAliasRecord
-AliasRecord
kUTTypeAppleICNS
-AppleICNS
kUTTypeAppleProtectedMPEG4Audio
-AppleProtectedMPEG4Audio
kUTTypeAppleProtectedMPEG4Video
-AppleProtectedMPEG4Video
kUTTypeAppleScript
-AppleScript
kUTTypeApplication
-Application
kUTTypeApplicationBundle
-ApplicationBundle
kUTTypeApplicationFile
-ApplicationFile
kUTTypeArchive
-Archive
kUTTypeAssemblyLanguageSource
-AssemblyLanguageSource
kUTTypeAudio
-Audio
kUTTypeAudioInterchangeFileFormat
-AudioInterchangeFileFormat
kUTTypeAudiovisualContent
-AudiovisualContent
kUTTypeAVIMovie
-AVIMovie
kUTTypeBinaryPropertyList
-BinaryPropertyList
kUTTypeBMP
-BMP
kUTTypeBookmark
-Bookmark
kUTTypeBundle
-Bundle
kUTTypeBzip2Archive
-Bzip2Archive
kUTTypeCalendarEvent
-CalendarEvent
kUTTypeCHeader
-CHeader
kUTTypeCommaSeparatedText
-CommaSeparatedText
kUTTypeCompositeContent
-CompositeContent
kUTTypeConformsToKey
-ConformsToKey
kUTTypeContact
-Contact
kUTTypeContent
-Content
kUTTypeCPlusPlusHeader
-CPlusPlusHeader
kUTTypeCPlusPlusSource
-CPlusPlusSource
kUTTypeCSource
-CSource
kUTTypeData
-Database
kUTTypeDelimitedText
-DelimitedText
kUTTypeDescriptionKey
-DescriptionKey
kUTTypeDirectory
-Directory
kUTTypeDiskImage
-DiskImage
kUTTypeElectronicPublication
-ElectronicPublication
kUTTypeEmailMessage
-EmailMessage
kUTTypeExecutable
-Executable
kUTExportedTypeDeclarationsKey
-ExportedTypeDeclarationsKey
kUTTypeFileURL
-FileURL
kUTTypeFlatRTFD
-FlatRTFD
kUTTypeFolder
-Folder
kUTTypeFont
-Font
kUTTypeFramework
-Framework
kUTTypeGIF
-GIF
kUTTypeGNUZipArchive
-GNUZipArchive
kUTTypeHTML
-HTML
kUTTypeICO
-ICO
kUTTypeIconFileKey
-IconFileKey
kUTTypeIdentifierKey
-IdentifierKey
kUTTypeImage
-Image
kUTImportedTypeDeclarationsKey
-ImportedTypeDeclarationsKey
kUTTypeInkText
-InkText
kUTTypeInternetLocation
-InternetLocation
kUTTypeItem
-Item
kUTTypeJavaArchive
-JavaArchive
kUTTypeJavaClass
-JavaClass
kUTTypeJavaScript
-JavaScript
kUTTypeJavaSource
-JavaSource
kUTTypeJPEG
-JPEG
kUTTypeJPEG2000
-JPEG2000
kUTTypeJSON
-JSON
kUTType3dObject
-k3dObject
kUTTypeLivePhoto
-LivePhoto
kUTTypeLog
-Log
kUTTypeM3UPlaylist
-M3UPlaylist
kUTTypeMessage
-Message
kUTTypeMIDIAudio
-MIDIAudio
kUTTypeMountPoint
-MountPoint
kUTTypeMovie
-Movie
kUTTypeMP3
-MP3
kUTTypeMPEG
-MPEG
kUTTypeMPEG2TransportStream
-MPEG2TransportStream
kUTTypeMPEG2Video
-MPEG2Video
kUTTypeMPEG4
-MPEG4
kUTTypeMPEG4Audio
-MPEG4Audio
kUTTypeObjectiveCPlusPlusSource
-ObjectiveCPlusPlusSource
kUTTypeObjectiveCSource
-ObjectiveCSource
kUTTypeOSAScript
-OSAScript
kUTTypeOSAScriptBundle
-OSAScriptBundle
kUTTypePackage
-Package
kUTTypePDF
-PDF
kUTTypePerlScript
-PerlScript
kUTTypePHPScript
-PHPScript
kUTTypePICT
-PICT
kUTTypePKCS12
-PKCS12
kUTTypePlainText
-PlainText
kUTTypePlaylist
-Playlist
kUTTypePluginBundle
-PluginBundle
kUTTypePNG
-PNG
kUTTypePolygon
-Polygon
kUTTypePresentation
-Presentation
kUTTypePropertyList
-PropertyList
kUTTypePythonScript
-PythonScript
kUTTypeQuickLookGenerator
-QuickLookGenerator
kUTTypeQuickTimeImage
-QuickTimeImage
kUTTypeQuickTimeMovie
-QuickTimeMovie
kUTTypeRawImage
-RawImage
kUTTypeReferenceURLKey
-ReferenceURLKey
kUTTypeResolvable
-Resolvable
kUTTypeRTF
-RTF
kUTTypeRTFD
-RTFD
kUTTypeRubyScript
-RubyScript
kUTTypeScalableVectorGraphics
-ScalableVectorGraphics
kUTTypeScript
-Script
kUTTypeShellScript
-ShellScript
kUTTypeSourceCode
-SourceCode
kUTTypeSpotlightImporter
-SpotlightImporter
kUTTypeSpreadsheet
-Spreadsheet
kUTTypeStereolithography
-Stereolithography
kUTTypeSwiftSource
-SwiftSource
kUTTypeSymLink
-SymLink
kUTTypeSystemPreferencesPane
-SystemPreferencesPane
kUTTypeTabSeparatedText
-TabSeparatedText
kUTTagClassFilenameExtension
-TagClassFilenameExtension
kUTTagClassMIMEType
-TagClassMIMEType
kUTTypeTagSpecificationKey
-TagSpecificationKey
kUTTypeText
-Text
kUTType3DContent
-ThreeDContent
kUTTypeTIFF
-TIFF
kUTTypeToDoItem
-ToDoItem
kUTTypeTXNTextAndMultimediaData
-TXNTextAndMultimediaData
kUTTypeUniversalSceneDescription
-UniversalSceneDescription
kUTTypeUnixExecutable
-UnixExecutable
kUTTypeURL
-URL
kUTTypeURLBookmarkData
-URLBookmarkData
kUTTypeUTF16ExternalPlainText
-UTF16ExternalPlainText
kUTTypeUTF16PlainText
-UTF16PlainText
kUTTypeUTF8PlainText
-UTF8PlainText
kUTTypeUTF8TabSeparatedText
-UTF8TabSeparatedText
kUTTypeVCard
-VCard
kUTTypeVersionKey
-VersionKey
kUTTypeVideo
-Video
kUTTypeVolume
-Volume
kUTTypeWaveformAudio
-WaveformAudio
kUTTypeWebArchive
-WebArchive
kUTTypeWindowsExecutable
-WindowsExecutable
kUTTypeX509Certificate
-X509Certificate
kUTTypeXML
-XML
kUTTypeXMLPropertyList
-XMLPropertyList
kUTTypeXPCService
-XPCService
kUTTypeZipArchive
-ZipArchive
次の例を参照してください。
using MobileCoreServices;
...
NSItemProvider itemProvider = new NSItemProvider ();
itemProvider.LoadItem(UTType.PropertyList ,null, (item, err) => {
if (err == null) {
NSDictionary results = (NSDictionary )item;
NSString baseURI =
results.ObjectForKey("NSExtensionJavaScriptPreprocessingResultsKey");
}
});
詳しくは、機能の使用に関するドキュメントのアプリ グループに関するセクションをご覧ください。
注意事項と考慮事項
拡張機能では、アプリより大幅に少ないメモリしか使用できません。 拡張機能は、ユーザーおよびそれがホストされているアプリへの関与を最小限に抑えながら、迅速に実行することが期待されています。 一方で、拡張機能では、それを使用するアプリに対する独自の便利な機能と、ユーザーが拡張機能の開発者または拡張機能が属するコンテナー アプリを識別できるブランド化された UI を、提供する必要もあります。
これらの厳しい要件を考えると、パフォーマンスとメモリの消費について徹底的にテストおよび最適化された拡張機能のみを展開する必要があります。
まとめ
このドキュメントでは、拡張機能の概要、拡張ポイントの種類、および iOS によって拡張機能に課される既知の制限について説明しました。 拡張機能の作成、配布、インストール、実行と、拡張機能のライフサイクルについて説明しました。 ストーリーボードまたはコードを使ってウィジェットの UI を作成する 2 つの方法を示す、簡単な Today ウィジェットを作成するチュートリアルを提供しました。 iOS シミュレーターで拡張機能をテストする方法を示しました。 最後に、ホスト アプリとの通信、および拡張機能を開発するときに留意する必要があるいくつかの注意事項と考慮事項について簡単に説明しました。