ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
macOSのショートカットについて
ショートカットがmacOSでも使えるようになるにあたり、Appがそのプロセスにおける重要パートを担います。Appの機能をショートカットアクションとして公開することで、Appの機能を向上させる方法を確認しましょう。Catalyst や AppKit で作成された macOS Appにおいてアクションを構築する方法、プラットフォーム間でアクションを展開する方法、ショートカットを公開して共有する方法、他のAppからのショートカットをAppで実行する方法について解説します。また、ショートカットがAutomatorやAppleScriptなどの既存のMacオートメーション技術とどのように連携するかについても説明します。
リソース
関連ビデオ
Tech Talks
WWDC21
-
ダウンロード
はじめまして! アヤカです 今日はMacの ショートカットを 紹介します
まずは簡単にMacの ショートカットの 概要から そして 今年更新される 内容についてお話します ショートカットを使えば 繰り返し作業を 自動化したり 異なる Appの機能をつなげて 問題に取り組む ことができます クリエイティブに テクノロジーを使えます Macのショートカットは 自動化をこれまで以上に 楽しく 身近なものにします
最初にAppを 開いた時点で すでにショートカットは iPhoneと同期されています Appにはショートカット ギャラリーも含まれており Mac独自のものなど 新しいショートカットが すでに使用可能です ご自分のショートカットを 作りたい場合は エディタで編集することもでき Macでの使用を前提に デザインを一新しました ちなみにMac Appは ほぼすべて SwiftUIで記述されています これにより iOSとMacのApp間で コードベースを統一しつつ プラットフォームに合わせた 細部調整が可能です
iOS同様 MacのどのAppでも ショートカットを 提供することが可能です このセッションでは iOSのApp MacのApp Mac Catalystで構築された Appのどの開発でも Appでショートカットを サポートすることで macOS上でのパワフルな ワークフローの実現について確認します
またAppleScriptやAutomator など既存ツールを含め Mac自動化における ショートカットの 位置づけについても 説明します
さらに今年は集中モードと サウンド認識の2つの オートメーションタイプが 追加されたほか ウィンドウやファイルを 管理するための 新しいアクションなど さまざまな機能が 追加されました
皆さんのAppでも カスタムアクションを提供し 強力なショートカットを 作る方法を紹介します
ファイルプロバイダーと 統合しているAppの場合 これらの新しい ファイルアクションは そのAppが提供する ファイルに自動で対応します また 既存のSiriKitの メッセージIntentを 採用している Appであれば カスタムのメッセージ送信 アクションを介し この機能を自動的に 使えるようにします
また 共有においても 開発の方に喜んでいただける 面白い機能がいくつか 追加されました 今年はショートカットの 配布を より簡単にしました また ショートカットを ファイルとして配布できる 新ファイル形式もあります また 仲間とショートカット を共有するのに便利な プライベート共有も 可能になりました 見てみましょう
特別な設定を 行わなくても ショートカットを ダウンロードできます つまり自分の WebサイトやAppで 独自のショートカットを 配布できるのです
例えばAppの アクションを利用した 素晴らしいショートカットを 提供したいとしましょう そのショートカットを 作成してiCloudに共有すると Appleがその ショートカットを公証して リンクに変えるので AppやWebサイトで紹介し 簡単にダウンロードして もらうことができます
ライブラリの写真のように Appにデータを渡す ショートカットを ダウンロードする際には より具体的な許可を求める メッセージを表示することで ユーザーが自分のデータを 管理できます 他にもあります ショートカットはファイル として共有することができ iCloud外で 配布するのに便利です iCloudのリンク同様 ショートカットファイルは Appleが安全性を公証します
最後に iCloudに アップロードせずに 個人的にショートカットを 共有する新しいモードがあり 連絡先での共有や個人的な バックアップに利用できます
ショートカットファイルは 送信した人のIDで 署名されます 署名し直したい場合は 新しいコマンドライン ツールを使います これが今年の ショートカットにおいて 更新された内容です ではショートカットが どのようにMacの 既存のオートメーション技術に適用 されるのか見てみましょう AppleScript Automator シェルスクリプトなど Macはオートメーションを 昔からサポートしています 今回 ショートカットが AppleScriptと シェルスクリプトをサポート するようになりました ショートカットに内蔵された 新しいアクションにおいて ショートカットエディタ内で 直接スクリプトを書いて 実行できます
次にAutomatorを 見てみましょう Automatorは反復的な 作業を自動化するための ワークフローを作成できる Mac既存の自動化ツールです ショートカットは Macの自動化の未来です ですがAutomatorは まだまだ大切で 頼るところも 大きいでしょう Automatorから ショートカットへの移行は 簡単です Automatorのワークフローを ショートカットに変換できる 移行ツールを作りました 例えば Finderで選択した PDFドキュメントから 画像作成するワークフローが あれば ショートカットは それをインポートして Automatorの各アクションを ショートカットアクションに 変えることができます
ワークフローファイルを ショートカットAppで ドラッグして開くか 右クリックメニューで 開くだけです あなたのワークフローが ショートカットになります migratorを有効にするため シェルスクリプトや AppleScriptを実行したり ファイル管理など 人気のAutomatorのアクションを ショートカットに加えました もちろん これらのアクションは migratorで サポートしています ショートカットをどのように Appに適用できるのか ユーザーにとっての 価値についてもお話します Appをショートカットと 統合する主な方法は アクションを介し 機能を公開することです ショートカットに アクションを公開することで ユーザーはAppの機能を 素早く使うことができ Appを活かした ワークフローが実現します そして 他のAppと組み合わせて 使うこともできます 詳しく見てみましょう macOSにはショートカットを 使える場所が たくさんあります 皆さんのAppの アクションを公開すると ショートカットAppや ショートカットメニュー またはキーボード入力や Spotlight検索で ユーザーは突然活用 できるようになります
つまり これまでよりも早く より多くの場所で 皆さんのAppの機能を 利用できるようになるのです さらにAppのアクションは 強力なマルチステップの ショートカットの 一部として使用できます 例えば タスクを 管理するための ToDoAppを 作ったとします Appの機能をアクション として公開すると突然 そのアクションを使って 強力なワークフローを 作ることができる ようになります 例えば 明日締め切りの ToDoをすべて検索し 優先度を高く設定し リストで表示する ショートカットを ワンクリックで 作成することができます アクションを フックする方法は 多岐にわたるため アクションをいくつか 公開するだけで Appの機能に何千もの ユースケースを実現できます また Appのアクションを 公開することで 他のAppと連携して 利用することもできます
例えば 画像編集Appを作って カスタム画像フィルターを 適用するアクションを 公開すると そのアクションを使って 写真を選択して 画像フィルターを適用し ワンクリックで SNSに投稿する というワークフローを 作ることができます アクションを公開することで Appでできることを 飛躍的に増やすことが できる例を紹介しました “優れたアクションのデザイン” のセッションでは Appを使って 人々を支えるために どのようなアクションを 提供できるか紹介しています ではイアンに引き継ぎます 彼がこれらのアクションを ビルドする方法を説明します どうも イアンです MacのAppの ショートカットアクションを 構築する方法を 紹介します 後で 統合のために 留意すべき 考慮事項について 説明します ではアクションを 構築しましょう ショートカットのアクション はSiriKitとしても知られる Intentsフレームワークを 使って構築されています このセッションでは 私が共同作業用の タスク管理Appを 開発中と仮定します タスクのリストを 期日ごとに記録し そのタスクを他の人と 共有することができる Appです このAppの機能を どのMacからでも 使えるようにすることが 重要です ショートカットと統合し タスクを含む複数ステップの ショートカットを構築 できるようになりました
Appを見てみると ユーザーが接する コンテンツの種類は 主にタスクです タスクにはいくつか 重要な属性があります まずタイトルですが これは何をするべきかを 説明するテキストです タスクには期日が設定され 他の人とタスクを 共有するための リンクもあります Appのタスクに関連して 実行できる操作が たくさんありますが これは 動詞と考えていいでしょう 新しいタスクを追加したり 既存のタスクを編集・削除 または特定の条件にマッチ したタスクを取得したり Appが特定のタスクを 表示することもできます これらの動詞に対し アクションを作ります これらはユーザーが 呼び出すことのできる 原始的な操作となり 人々が一緒に 使うことができるよう 設計されています
例えば タスクを取得して それを削除する これをペアにすることができます
タスクを作成するだけで ノートから選択された テキストを取得し 必要に応じて テキストを大文字にし ユーザーに期日の入力を 求める自動ワークフローを 作成することができます ショートカットは そのテキストと期日を タスクの作成 アクションに渡し タスクを作成する意図を Appに伝えます このAppで タスクが作成されると Intentは 共有URLを含むタスクを返し ショートカットは そのリンクをメッセージで 誰かに送ることができます
これはほんの一例です ショートカットには 多くのアクションがあるため タスク作成アクションの 活用方法は ほぼ無限大です Appが公開する アクションがわかったので 早速 実装してみましょう
実装の最初のステップは AppのIntent定義 ファイルの作成です このファイルをXcode プロジェクトに追加すると ソースコードと 一緒にコンパイルされます
今回は Mac用の かなりベーシックな SwiftUI Appを 用意しました ですが 使用している UIフレームワークに関わらず Intentを追加する プロセスは非常に似ています まずは Intent定義 ファイルを作成します
テンプレートの選択画面で Intent定義を検索します
デフォルトの名前で 大丈夫です Appのターゲットに追加 してコンパイルしてください
これが新しい空の Intent定義ファイルです
このファイル内ではAppが 実装するIntentと 今回のケースでは タスクのように使用される 他のタイプを 定義する必要があります このAppには いくつかのIntentがあり それぞれがこのタスク タイプを参照するので まずタスクタイプを 定義します そのためには左下の +ボタンをクリックし 新しいタイプを選択します
各タイプには 自動的にidentifierと displayString プロパティが設定されます
タスクのタイトルには displayStringプロパティが 使えますが 期日や共有リンクには タイプにさらにプロパティを 追加する必要があります まず タスクがいつまでに 完了しなければならないか dueDate(期日)の プロパティを追加します
ここでは 日付と時間を 表現するため TypeはDate Components を選択します
また 他の人と共有できる タスクへのリンクである URLも追加します
最初のタスクが 完了したので 次はIntentを 定義していきます これはタスクを作成する ためのIntentになります
“Create Task”と 名前をつけて カテゴリを “Create”に変更します
またこの Intentについて 簡単な説明を加えます
ユーザーはAppが実装する アクションのリストを 見ているときに これを見ることになります 次に Intentの パラメータを定義します Intentは 関数のようなもので パラメータはその関数が 仕事をするために 渡さなければならない 引数と考えてください
新しいタスクを 作成するためには タイトルと期日が 必要なので それぞれの パラメータを作成します 共有リンクはタスク作成時に Appが生成するため 前もって収集する 必要はありません まずパラメータにタイトルを 追加してみましょう
TypeはStringを 選択します
各タイプのパラメータには いくつかのオプションが 設定されています デフォルト値を 指定することができますが 今回はstringを 空にしておきます 次にDueDateパラメータで Date Components タイプに設定します
プロンプトを追加します 事前に期日を入力しない場合 Appは実行時に期日の 入力を求めることができます
両方のパラメータが定義 されたので ほぼ完了です 最後に ショートカット Appのセクションで いくつか項目を 記入します まずInput Parameterを 定義します ユーザーはどこかから テキストを受け取り それをアクションに 渡す場合が多いので Input Parameterで Titleを選択します これで ショートカットを 作成する際の操作性が向上し 前のアクションのテキストが ショートカットにある場合 アクションを追加する際に 自動的に入力されます 最後にSummaryを 定義します この文字列は ユーザーが ショートカットで このアクションを 見たときに表示されます “Create task with due date”とします
どちらのパラメータも タスクの作成に必要で アクションを見ると 常に表示されるように 両方ともsummaryに 入れています 他にもパラメータがあって それをSummaryに 入れたくない場合は 省略しても構いません その場合 ユーザーが アクションを展開し 編集できます
Intentを定義すると Xcodeは実装に 使用するためのクラスと プロトコルを生成します ではAppをビルドします 続行する前に Intent定義に 問題がないことを 確認します ビルドログに新しいコードが ビルドされています これらのソースファイルは Intent定義に書かれた 内容に基づき Xcodeによって生成されます Xcodeは Taskタイプと CreateTaskIntentの ソースコードを 生成しました
これらはコードベースで 使用できるようになりました さらにIntentが 定義されたので Appを起動して新しい ショートカットを作成し 新しいCreate task アクションを検索します コードを書いてないので このままでは動作しません 次に Appは受信した Intentを ディスパッチして 処理する必要があります このコードは 誰かが ショートカットやSiriで アクションを実行すると 実行されます
Appはどのプロセスで Intentを処理するか 決める必要がありますが 選択肢は2つあります 1つ目はIn-appで 処理すること 2つ目は別の Intents Extensionを 構築することです
In-appの Intentハンドリングでは Appデリゲートに 配信されるIntentに応じ Appの状態を 操作することができます ほとんどのAppは これでスタートし 必要に応じてIntents Extensionに移行します
Intents Extensionは Appに代わって Intentを処理できる 軽量の スタンドアロンプロセスです Intentを 実行するたびに プロセスを起動する 必要があるため Intents Extensionを 構築することで Appを起動する必要が なくなり 効率的になります
AppとIntents Extension は受信したIntentに対し Intentハンドラの インスタンスを求められます これを実現するために実装 すべきAPIを見てみましょう
In-appの Intentハンドリングのために Appデリゲートメソッド がありハンドラを返します
macOS Montereyでは NSApplicationの DelegateでもこのAPIが 利用できるようになりました Intents Extensionsの場合 INExtensionのサブクラスで handlerForIntentメソッド をオーバーライドできます
In-app Intentハンドリングと Intents Extensionsの 違いについては WWDC 2020の “インテントの強化” セッションをご覧ください
今度はAppの中に入って In-appのIntentハンドリングを 実装してみます まず CreateTaskIntentを Appのサポートされた Intentとして 追加することです これによりシステムは このIntentがAppの デリゲートに配信されるべき だと知ることができます
次に メインAppのソース コードにアクセスします
まず Appのデリゲートを 作成します SwiftUIAppのライフ サイクルを使用しているので オブジェクトをSwiftUIに フックするための デリゲートアダプターも 作成する必要があります
Intentを受け取るには intentsフレームワークを インポートし handlerFor Intentメソッドを実装します
ディスパッチメソッドが できたので 次はIntentの ハンドラを実装します
この例ではインテントを 処理するために 新しいクラスを 作成しますが これはApp内の任意の オブジェクトで構いません Intentハンドラは “Handling”で終わる このIntentのために 生成されたプロトコルに 従わなければなりません Create Taskインテント については CreateTaskIntentHandling という名前になっています
そしてIntentが CreateTaskIntentの場合 新しいIntentHandler オブジェクトを返すように handlerForIntent メソッドを更新します
Xcodeが どのメソッドの 実装が必要か教えてくれます
Intentハンドリングの プロトコルには resolve provide options confirm そしてhandleの 4種類のメソッドがあります resolveメソッドは 各パラメータに応じたものが 生成されます これはパラメータが有効か チェックし 無効の場合 どうすべきか システムに 伝えることができます
このIntentには2つの resolveメソッドがあります タイトルと期日です
titleパラメータでは 値がnilではなく 空でもないことをAppが 確認する必要があります
titleが空だと needsValueを返し 値を入力するよう ユーザーに促します
due dateパラメータでは 同様のものが 実装されていて 期日がnilではないことを 確認するため 検証します Appの期日に関する 要件を見ると resolveメソッドは 期日が未来か確認し 期日が過去の場合 エラーを表示する 必要があります
Intent定義ファイルに戻り カスタムの 検証エラーを追加します
invalidDate というコードと “未来の期日を 入力してください”と エラーメッセージを 入力します
これでAppは先ほど追加 したカスタム検証エラーで サポートされていない 結果を返すことができ 日付が無効で あることを示す エラーメッセージが 表示されるようになりました もう1つは provide optionsです Dynamic Optionsの設定が 有効になっている パラメータでは こちらが表示されます 任意の値を入力 できないようにし 有限のセットのパラメータが ある場合は そのパラメータに対し ダイナミックオプションを 有効にすることが できます
そしてIntent実行時に 可能な値を指定できる provide optionsという メソッドが作成され ショートカットはその 値の中から選択を求めます パラメータが個別に resolveされた後 confirmメソッドも 実装することができます ここでは すべてが問題なく 見えることを確認し Intentを処理できるようにし 問題があれば エラーを投げるように する必要があります 例えば ネットワーク 接続が必要な場合は ここで実際に ネットワークに到達できるか 確認します
最後に ハンドルメソッドでは Intentが指示したことを 実際に実行します
ハンドルメソッドは App内にタスクを作成し 成功を返します アクションが タスクを作成した後 タスクを出力として提供し 後のアクションが 共有URLなどの属性を 使用できるようにする 必要があります Intentエディタに戻り Intentのレスポンス セクションに移動します ここに作成したタスクの プロパティを追加します
先に作ったタスクタイプを 選択します そのプロパティをIntentの 出力として選択して ショートカットで利用 できるようにします コードに戻るとレスポンスに タスクのプロパティがあり タスクオブジェクトを 割り当てることができます
これが完了すると Intentがアクション として表示されます 必ず時間をかけて テストを行い ユーザーがどのように 使うかを試してください まずショートカット Appを開き タスクの作成アクションを 見てみましょう
タスクの名前と日付 “Tomorrow at 3pm”を 記入します
実行ボタンを押すと タスクが正常に 作成されます ですが “Yesterday at 3pm”に変更すると
エラーメッセージが 表示されます ショートカットでアクション を構築する方法でした 最後に Macで ショートカットを 開発する際に 注意すべき点を ご紹介します
Mac Catalystで 作られたApp ファイルを扱うApp iOSとMacの両方に デプロイするApp ショートカットが必要な Appやツールなど 4種類のAppを紹介します
まずMac Catalystを 使ってiOS Appを Macに導入する場合 知っておくべきことがあります macOS Montereyでは Mac Catalystで構築された Appであれば 同じ Intents APIを使用できます 新しいAppを作る場合は すべてがiOSと同じように 動作する必要があります
しかし あなたのAppが すでにMacで提供されていて iOSでIntentsフレームワーク を実装している場合 Macに来る過程で Intentのインテグレーションを コンパイルから外してしまった 可能性があります macOS Monterey上で 動作させる場合は コードを監査してこの機能を 再度有効にしてください
もう一つのタイプのApp として注目したいのが ドキュメントベースの Appです 先ほどのデモでは タスク管理Appが App内に完全に存在する タスクを操作していますが Appの多くは ディスク上のファイルとして 存在するドキュメントを 操作します iOS 15と macOS Montereyでは Intentのファイルパラメータによって 特定のファイルを選択し Appのアクションに 渡すことができます データベースではなく 主にファイルベースの ドキュメントを扱う Appの場合は ファイルを操作する アクション群の構築を 検討してください 例えば表計算Appでは ドキュメントを開く アクションや 特定のファイルの行に データを追加する アクションを作成できます
サードパーティの ショートカットを ファイルに適用した 例としては Sound Analysisの セッションがあります
iOSとMacの両方に対応する Appを開発する場合 アクションを両方で 動作させるには Appの両方のコピーに 同じIntentを 配置する必要があります これにより 一方で ショートカットを構築し もう一方でも同じように 動作させることができます そのためには 両方のAppに 同じIntent定義を コンパイルして Intentの名前とパラメータが 同じになるようにします
プラットフォームに よっては Appのバンドル識別子が 異なる場合がありますが 問題ありません ただし 同じ名前のIntentは 同じApple Developer Team経由で デプロイされた App間でのみ 共有されることに 留意してください
また iOS 15では あるバージョンのAppから 別のバージョンのAppに アップグレードする場合 2つのAppが 同じ開発者によるもので 同じIntent名を 使用していれば Appのショートカットは 自動的に一方から他方に 引き継がれます 最後に Appやコマンド ラインツールから ショートカットを実行する 場合 2つの方法があります まずショートカットは Mac AppやAppleScriptsが ショートカットをリスト アップして実行するための スクリプトインターフェイス を公開します さらに シェルスクリプトや コマンドラインツールで ショートカットをリスト アップして実行できる ショートカットコマンド ラインツールもあります
ショートカットを実行する 機能が必要なAppや スクリプトを 開発する場合は スクリプティングインター フェースを使用してください ショートカットイベントの プロセスと通信することで Appはユーザーが設定した ショートカットのリストを 取得したり ショートカット の実行を開始できます
AppleScriptではショート カットイベントのプロセスに 名前でショートカットを 実行させ実現できます
Scripting Bridge フレームワークを使えば SwiftやObjective-CのApp から直接ショートカットを 実行するよう プロセスに依頼できます サンドボックスAppにおいては “com.apple.security.scripting-targets” というEntitlementを追加します
ショートカットのリストに アクセスして実行するために “com.apple.shortcuts.run”を ターゲットに追加する 必要があります 最後に macOS Montereyには ショートカット一覧表示し 名前で実行できるコマンド ラインツールもあります コマンドラインツールや スクリプトをお持ちの方は これを介しショート カットと統合できます まとめます Macでショートカット が利用できるようになり どんなAppでも ショートカットのエコ システムに参加すべきです Appにアクションを 追加することで 人々ができることを 増やすことができます ショートカットでAppを クリエイティブに使えます ユーザーがどんなショート カットを構築するのかを 知ることは とても 楽しいことでしょう [音楽]
-
-
17:10 - Adding Intent dispatch method in SwiftUI
import SwiftUI import Intents @main struct SouperTaskApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { WindowGroup { ContentView() } } } class AppDelegate: NSObject, NSApplicationDelegate { func application(_ application: NSApplication, handlerFor intent: INIntent) -> Any? { } }
-
18:32 - Resolve intent
class IntentHandler: NSObject, CreateTaskIntentHandling { func resolveTitle(for intent: CreateTaskIntent, with completion: @escaping (INStringResolutionResult) -> Void) { guard let title = intent.title, !title.isEmpty else { return completion(.needsValue()) } return completion(.success(with: title)) } func resolveDueDate(for intent: CreateTaskIntent, with completion: @escaping (CreateTaskDueDateResolutionResult) -> Void) { guard let dateComponents = intent.dueDate else { return completion(.needsValue()) } return completion(.success(with: dateComponents)) } ... }
-
19:37 - Date range validation in dueDate resolve method
func resolveDueDate(for intent: CreateTaskIntent, with completion: @escaping (CreateTaskDueDateResolutionResult) -> Void) { guard let dateComponents = intent.dueDate, let dueDate = Calendar.current.date(from: dateComponents) else { return completion(.needsValue()) } if dueDate < Date() { return completion(.unsupported(forReason: .invalidDate)) } return completion(.success(with: dateComponents)) }
-
20:40 - Handle intent
class IntentHandler: NSObject, CreateTaskIntentHandling { func handle(intent: CreateTaskIntent, completion: @escaping (CreateTaskIntentResponse) -> Void) { let title = intent.title! let dueDate = intent.dueDate! let task = createTask(name: title, due: dueDate) let response = CreateTaskIntentResponse(code: .success, userActivity: nil) response.task = task completion(response) } }
-
25:39 - Running Shortcut from AppleScript
tell application "Shortcuts Events" run the shortcut whose name is "Make GIF" end tell
-
25:49 - Using scripting bridge
import ScriptingBridge @objc protocol ShortcutsEvents { @objc optional var shortcuts: SBElementArray { get } } @objc protocol Shortcut { @objc optional var name: String { get } @objc optional func run(withInput: Any?) -> Any? } extension SBApplication: ShortcutsEvents {} extension SBObject: Shortcut {} guard let app: ShortcutsEvents = SBApplication(bundleIdentifier: "com.apple.shortcuts.events"), let shortcuts = app.shortcuts else { print("Couldn't access shortcuts") return } guard let shortcut = shortcuts.object(withName: "Make GIF") as? Shortcut else { print("Shortcut doesn't exist") return } _ = shortcut.run?(withInput: nil)
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。