【Swift】App Intents を触ってみる
初めに
App Intents とは、ユーザーがアプリのタスクを素早く実行できるようにするフレームワークです。
App Inetnts は2022年のWWDCで発表され、2024年のWWDCでもアップデートが発表されていました。
システムの利便性を高めるApp Intentのデザイン の動画では、「App Intents contain all the tasks your app can do」という部分があり、 Apple Intelligence などと組み合わせて今後活発に使用されるようになることが予想されています。
今回は App Intents を触ってみて、簡単にどのようなことができるかまとめてみたいと思います。
記事の対象者
- Swift, SwiftUI 学習者
- App Intents に触れてみたい方
目的
今回の目的は先述の通り、App Intents に触れてみることです。
本来であればショートカットアプリで他のアプリと連携してタスクを実行することもできるのですが、今回は比較的単純なタスクを App Intents から呼び出すような実装を行います。
App Intents とは
App Intents について、公式ドキュメントの App Intents - Overview から引用します。
The App Intents framework provides functionality to deeply integrate your app’s actions and content with system experiences across platforms, including Siri, Spotlight, widgets, controls and more. With Apple Intelligence and enhancements to App Intents, Siri suggests your app’s actions to help people discover your app’s features and gains the ability to take actions in and across apps.
(日本語訳)
App Intentsフレームワークは、Siri、Spotlight、ウィジェット、コントロールなど、プラットフォーム全体のシステムエクスペリエンスとアプリのアクションやコンテンツを深く統合するための機能を提供します。AppleのインテリジェンスとApp Intentsの強化により、Siriはアプリのアクションを提案してアプリの機能を人々に発見させる手助けを行い、アプリ内およびアプリ間でアクションを実行できるようになります。
上記をまとめると以下のようなことが言えるかと思います。
- App Intents を使えばプラットフォームのシステムとアプリを統合できる
- Siri からアプリ内のアクションを呼び出すことができる
実装
実装は以下の手順で進めていこうと思います。
- シンプルな App Intents の実装
- アプリを開く App Intents の実装
- ダイアログを開く App Intents の実装
- タイマーの開始 / 終了ができる App Intents の実装
1. シンプルな App Intents の実装
まずは最もシンプルな形の App Intents を実装してみます。
なお、今回は全て実機で試していきます。
コードは以下の通りです。
AppIntent
では title
と perform
を実装しています。
title
は名前の通り AppIntent
のタイトルであり、ショートカットアプリなどで表示される名前を設定できます。
perform
ではその AppInetnt
が呼び出された際に実行する内容を定義することができます。
上記のコードでは返り値を IntentResult
として、単純に文字列を print して、空の result を返すようにしています。
import AppIntents
struct SimpleIntent: AppIntent {
static let title: LocalizedStringResource = "Simple Intent"
@MainActor
func perform() async throws -> some IntentResult {
print("Simple Intent performed !")
return .result()
}
}
この SimpleIntent
を追加したアプリケーションを実行して、以下の動画のようにショートカットアプリを開き、 SimpleIntent
を実行するとコンソールに「Simple Intent performed !」と表示されているかと思います。
これで AppIntent
が正常に動作していることが確認できます。
見た目としては以下の動画のように変化がないことがわかります。
2. アプリを開く App Intents の実装
次に、アプリを開く AppIntent
を実装していきます。
コードは以下の通りです。
基本的には先程の実装と同様ですが、 openAppWhenRun
というパラメータを true
にしています。このようにすることで、名前の通りこの OpenAppIntent
が呼び出された際にはアプリが起動するようになります。
import AppIntents
struct OpenAppIntent: AppIntent {
static let title: LocalizedStringResource = "Open App Intent"
static var openAppWhenRun: Bool = true
@MainActor
func perform() async throws -> some IntentResult {
print("Open App Intent performed !")
return .result()
}
}
これで実行すると、以下の動画のように App Intents が実行されるとアプリが開くことが確認できます。
3. ダイアログを開く App Intents の実装
次にダイアログを開く AppIntent
を実装していきます。
コードは以下の通りです。
以下では返り値を ReturnsValue<String> & ProvidesDialog
のようにしています。これで、 result
として value
と dialog
を返すことができます。
value
ではこの ShowDialogAppIntent
が実行された際にアプリ側に渡したい値を設定することができます。この実装では特に渡す値はないためからの文字列にしています。
dialog
では IntentDialog
を渡すことができ、そこに表示させたい文言を入れることで、ダイアログに文言を表示させることができるようになります。
import AppIntents
struct ShowDialogAppIntent: AppIntent {
static let title: LocalizedStringResource = "Show Dialog Intent"
static var openAppWhenRun: Bool = true
@MainActor
func perform() async throws -> some ReturnsValue<String> & ProvidesDialog {
return .result(value: "", dialog: IntentDialog("Welcome back !"))
}
}
上記のコードで実行すると、以下の動画のようにアプリが開き、それと同時にダイアログが表示されることがわかります。
4. タイマーの開始 / 終了ができる App Intents の実装
最後にアプリ内のタイマーを開始 / 終了できる AppIntent
を実装していきます。
まずはアプリ内で実行できるタイマーを作成していきます。
タイムを Manager で管理するために TimerManager
を作成します。
コードは以下です。
seconds
で秒数を保持して、タイマーのスタート、ストップ、リセットができるようにしています。
また、 shared
で外部から TimerManager
を参照できるようにしています。
import Foundation
class TimerManager: ObservableObject {
static let shared = TimerManager()
@Published var seconds: Int = 0
private var timer: Timer?
private var startDate: Date?
private init() {}
func startTimer() {
guard timer == nil else { return }
startDate = Date()
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
self?.seconds += 1
}
}
func stopTimer() {
timer?.invalidate()
timer = nil
startDate = nil
}
func resetTimer() {
stopTimer()
seconds = 0
}
}
次にビューを作成していきます。
コードは以下の通りです。
TimerManager.shared
で TimerManager
にあるタイマーのスタート、ストップ、リセットを実行できるようにしています。
import SwiftUI
struct TimerView: View {
@ObservedObject private var timerManager = TimerManager.shared
var body: some View {
VStack(spacing: 20) {
Text("Timer")
.font(.largeTitle)
.padding()
Text("\(timerManager.seconds) seconds")
.font(.title)
.foregroundColor(.green)
HStack(spacing: 20) {
Button(action: {
timerManager.startTimer()
}) {
Text("Start")
}
.buttonStyle(.borderedProminent)
Button(action: {
timerManager.stopTimer()
}) {
Text("Stop")
}
.buttonStyle(.borderedProminent)
Button(action: {
timerManager.resetTimer()
}) {
Text("Reset")
}
.buttonStyle(.bordered)
}
}
.padding()
}
}
次にタイマーを開始する AppIntent
を作ります。
コードは以下の通りです。
perform
メソッドの中で TimerManager.shared.startTimer()
を実行することで、タイマーをスタートしています。
import AppIntents
struct StartTimerIntent: AppIntent {
static var title: LocalizedStringResource = "Start Timer"
static var description = IntentDescription("Starts the timer.")
static var openAppWhenRun: Bool = true
func perform() async throws -> some IntentResult & ProvidesDialog {
await MainActor.run {
TimerManager.shared.startTimer()
}
return .result(dialog: "Timer started.")
}
}
次にタイマーを終了する AppIntent
を作ります。
コードは以下の通りです。
perform
メソッドの中で TimerManager.shared.stopTimer()
を実行することで、タイマーをストップしています。
また、タイマーをストップした段階で経過時間を音声で読み上げるようにしています。
今回はこれらの AppIntent
をショートカットアプリから呼び出していますが、追加の対応を入れると Siri でも使用できるため、一連の処理を Siri で行えば、スマホを触ることなくアプリ内のタイマーの開始 / 終了ができ、終了時間も知ることができるようになります。
import AppIntents
import AVFoundation
struct StopTimerIntent: AppIntent {
static var title: LocalizedStringResource = "Stop Timer"
static var description = IntentDescription("Stops the timer.")
static var openAppWhenRun: Bool = true
func perform() async throws -> some IntentResult & ProvidesDialog {
let elapsedSeconds: Int = await MainActor.run { () -> Int in
TimerManager.shared.stopTimer()
return TimerManager.shared.seconds
}
// 時間をフォーマット
let formattedTime = formatTime(seconds: elapsedSeconds)
let utterance = AVSpeechUtterance(string: "タイマーを停止しました。経過時間は \(formattedTime) です。お疲れ様でした。")
// 喋る言語の設定
utterance.voice = AVSpeechSynthesisVoice(language: "ja-JP")
utterance.rate = 0.5
let synthesizer = AVSpeechSynthesizer()
synthesizer.speak(utterance)
return .result(dialog: "タイマーを停止しました。経過時間は \(formattedTime) です。")
}
func formatTime(seconds: Int) -> String {
let hours = seconds / 3600
let minutes = (seconds % 3600) / 60
let secs = seconds % 60
var components: [String] = []
if hours > 0 {
components.append("\(hours)時間")
}
if minutes > 0 {
components.append("\(minutes)分")
}
if secs > 0 || components.isEmpty {
components.append("\(secs)秒")
}
return components.joined()
}
}
上記のコードで実行すると以下のようにタイマーの開始 / 終了ができることがわかります。
まとめ
最後まで読んでいただいてありがとうございました。
今回は App Intents について簡単に触れてみました。
今回は紹介できませんでしたが、AppShortcutsProvider
に App Intents を登録することで Siri からも呼び出せるようになるため、非常に便利になるかと思います。
誤っている点やもっと良い書き方があればご指摘いただければ幸いです。
参考
Discussion