SiriKit Custom Intent Shortcut 開發紀錄(一)-continueInApp
Swift 5, Xcode 14.3.1
source code: github
大綱
研究目的: 讓app可透過siri語音輸入後執行相關指令
方式:本文主要紀錄以custom intent方式,透過建立「捷徑」,來讓app可以收到語音資料以便後續處理,其中處理資料方式有兩種。
其一為收到資料後的處理邏輯在intent extension處理,而如果與主程式target有共用邏輯,可以將共用程式碼抽離製作成新的framework,在主程式與intent extension import使用。
本文示範使用另一種方式:將資料傳給主程式,在主程式處理邏輯,下一篇將示範如何製作framework,在intent extension中直接處理指令。
步驟
- 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」加上你設定的捷徑名稱觸發。