SiriKit Custom Intent Shortcut 開發紀錄(一)-continueInApp

Bangkangchen
11 min readNov 6, 2023

--

Swift 5, Xcode 14.3.1
source code: github

大綱

研究目的: 讓app可透過siri語音輸入後執行相關指令

方式:本文主要紀錄以custom intent方式,透過建立「捷徑」,來讓app可以收到語音資料以便後續處理,其中處理資料方式有兩種。
其一為收到資料後的處理邏輯在intent extension處理,而如果與主程式target有共用邏輯,可以將共用程式碼抽離製作成新的framework,在主程式與intent extension import使用。
本文示範使用另一種方式:將資料傳給主程式,在主程式處理邏輯,下一篇將示範如何製作framework,在intent extension中直接處理指令。

步驟

  1. Create extension target

要在ios中支援捷徑,我們需要在專案內新增intent extension的target

在xcode project 中,新增target,類型選擇intent extension(App Intents Extension應是iOS 16.0以上新增使用純coding方式製作intent definition,筆者還沒研究)

名稱可依照用途命名,其中Starting Point 因為我們要使用客製化intent,所以選擇None, Include UI Extension 因為這次沒用到,可選可不選

建立完成後跳出的詢問視窗,若選Activate,應只是把目前的scheme切換過去新建立的target對應的scheme而已,不影響後續操作

以上我們就新增完intent extension了,接下來可以進行下一階段

2. 建立.intentdefinition 檔案

.intentdefinition 定義了整個intent的資料架構及在捷徑中運行時的行為模式

在project navigator 中新建檔案,選擇SiriKit Intent Definition File,檔名依據需求命名,注意確認Targets至少需勾選到剛剛建立的Intent extension,如果後續要製作建議捷徑時,則需要再勾選主app的target

新增Intent:左下方 + , 選New Intent,然後將其命名為喜歡的樣子

再來就是設定Intent內容,這裡的Title和Description會顯示在捷徑的選項中

設定參數欄位,在Parameters中點 “+”,建立會使用到的參數,並修改名稱,新增Siri Dialog問句內容

最後設定shortcuts app 上的描述”Summary”(下方Suggestions會自動同步)

由於我們要將資料傳入Main App target中,Response可以先只設定failure的部分,至此完成intentdefinition file 的建置。

3. IntentHandler邏輯

在原本的IntentHandler.swift中將handler function改寫成這樣,這裡的SiriDeviceOperationInfoIntent是剛剛定義好的custom intent名字加上Intent,透過下面這個方式來將多個intent處理方式分流。

    override func handler(for intent: INIntent) -> Any {
if intent is SiriDeviceOperationInfoIntent {
return SiriDeviceOperationInfoIntentHandler()
}
else {
fatalError("Unhandled Intent error : \(intent)")
}

}

建立對應的handler class, ex: SiriDeviceOperationInfoIntentHandler.swift
遵循SiriDeviceOperationInfoIntentHandling(”SiriDeviceOperationInfo”+ IntentHandling)

class SiriDeviceOperationInfoIntentHandler: NSObject, SiriDeviceOperationInfoIntentHandling {
}

此時會跳出提示缺少協議內的函式,需補上resolveXXX,其作用是在使用者填入intent對應欄位的內容時做預處理及系統的回應(例如回傳needsValue的話就會要求重新輸入,此處的completion回傳結果種類可參照apple文件),範例如下:

func resolveDeviceName(for intent: SiriDeviceOperationInfoIntent, with completion: @escaping (INStringResolutionResult) -> Void) {
if let deviceName = intent.deviceName,
deviceName.count > 0,
deviceName != "裝置名稱"{
completion(INStringResolutionResult.success(with: deviceName))
}
else {
completion(INStringResolutionResult.needsValue())
}
}

最後實作handle function如下,因傳入的intent 欄位資料型別會是optional,可再一次處理資料。傳入main app的關鍵是建立NSUserActivity,自訂義activityType內容,透過addUserInfoEntries填入資料,透過completion傳入對應intent response,並將response初始化參數帶入.continueInApp及建立好的userActivity。

func handle(intent: SiriDeviceOperationInfoIntent, completion: @escaping (SiriDeviceOperationInfoIntentResponse) -> Void) {

guard let deviceName = intent.deviceName,
let operation = intent.operation
else {
completion(SiriDeviceOperationInfoIntentResponse(code: .failure, userActivity: nil))
return
}

let activity = NSUserActivity(activityType: "tw.com.bk.DeviceOperationActivity")

activity.addUserInfoEntries(from: [
"deviceName" : deviceName,
"operation" : operation
])

completion(SiriDeviceOperationInfoIntentResponse(code: .continueInApp, userActivity: activity))
}

以上步驟我們已經完成main App以外的工作,接下來處理main App

4. 處理main App

新增main App 的capability:在xcodeproj介面選取main App target,切換至Signing & Capabilities 點選+Capability,新增Siri

在main App的info.plist 新增屬性”Privacy - Siri Usage Description”

最後再依據你的專案
有使用SceneDelegate的話需在SceneDelegate內新增以下function

func scene(_ scene: UIScene, continue userActivity: NSUserActivity){
}

只有AppDelegate的話在AppDelegate內新增

func application(_ application: UIApplication, 
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {

}

其中userActivity用法都一樣,以SceneDelegate為範例如下:

func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
print("continue userActivity in scene")

if userActivity.activityType == "tw.com.bk.DeviceOperationActivity" {
// Extract parameters and perform actions.
guard let deviceName = userActivity.userInfo?["deviceName"] as? String,
let operation = userActivity.userInfo?["operation"] as? String
else { return }

print("application userActivity : deviceName = \(String(describing: deviceName)), operation = \(String(describing: operation))")
}
}

依據activityType去判斷是哪個intent導過來的,再透過userActivity.userInfo取出資料,就可以在app內進行後續的動作了。

使用方式

手機打開「捷徑」or 「shortcut」,新增捷徑,在app中找到你的app,如果以上設置都正確的話,就會出現可以加入的捷徑。之後就可以使用「hey, Siri」加上你設定的捷徑名稱觸發。

參考:

--

--

Bangkangchen

Hello, my name is 陳邦亢, but you can call me Mars. I am an iOS app development engineer with 4 years of experience, based in Taiwan.