如何管理 Siri 意图启动我的应用程序并将数据传输到应用程序?

How to manage that Siri intent launches my app and transfers data to the app?

经过几个小时的搜索,我请求你的帮助。

我正在编写一个应用程序来管理我的库存。作为通过专用视图“AddNewEntry”手动输入库存项目的替代方法,我想使用 Siri 和 intent 来捕获“Store item 意义上的数据 放在 部分 中”。 item, place 和 section 是我想通过 Siri 获取的属性,然后将它们存储在我的数据库使用视图“AddNewEntry”,我在其中处理这些数据的手动输入、检查和存储。对于来自意图的新数据的移交,我想 post 向我的 mainViewController 发出通知,然后启动 segue 到要显示的视图和 check/store 新条目。

我已经通过意图定义设置了意图(“VorratAdd”),快捷方式似乎工作正常并收集数据。但是快捷方式不会 return 到我的应用程序,并以弹出视图结束,消息如下(我从德语翻译):

"Vorrat Add" couldn't be executed. Tasks takes too long to be closed. Retry again"

我的设置如下:

我的意图处理程序 VorratAdd:

import Foundation
import Intents
import UIKit

class VorratAddIntentHandler: NSObject, VorratAddIntentHandling {
    func handle(intent: VorratAddIntent, completion: @escaping (VorratAddIntentResponse) -> Void) {
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: "NewVorratInputFromSiri"), object: intent)
    }
    
    func resolveBezeichnung(for intent: VorratAddIntent, with completion: @escaping (INStringResolutionResult) -> Void) {
        if intent.item == " " {
            completion(INStringResolutionResult.needsValue())
        } else {
            completion(INStringResolutionResult.success(with: intent.bezeichnung ?? "_"))
        }
    }
    
    func resolveOrt(for intent: VorratAddIntent, with completion: @escaping (INStringResolutionResult) -> Void) {
        if intent.place == " " {
            completion(INStringResolutionResult.needsValue())
        } else {
            completion(INStringResolutionResult.success(with: intent.ort ?? "_"))
        }
    }

    func resolveFach(for intent: VorratAddIntent, with completion: @escaping (VorratAddFachResolutionResult) -> Void) {
        if intent.section == 0 {
            completion(VorratAddFachResolutionResult.needsValue())
        } else {
            completion(VorratAddFachResolutionResult.success(with: intent.fach as! Int))
        }
    }
    
    
}

IntentHandler.swift:

import UIKit
import Intents


class IntentHandler: INExtension {
    
    override func handler(for intent: INIntent) -> Any {
        guard intent is VorratAddIntent else {
            fatalError("unhandled Intent error: \(intent)")
        }
        return VorratAddIntentHandler()
    }
}

捐赠 在我的 mainViewController“HauptVC”中:

extension HauptVC {
    
    func initSiriIntent() {
        let intent = VorratAddIntent()
        intent.suggestedInvocationPhrase = "Neuer Vorrat"
        intent.item = " "
        intent.location = " "
        intent.section = 0
        
        let interaction = INInteraction(intent: intent, response: nil)
       
        interaction.donate { (error) in
            if error != nil {
                if let error = error as NSError? {
                    print("Interaction donation fehlgeschlagen: \(error.description)")
                } else {
                    print("Interaction donation erfolgreich")
                }
            }
            
        }
    }
}

AppDelegate:

import UIKit
import Intents


@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    var rootViewController: UINavigationController?
    
    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
        if userActivity.activityType == "VorratAddIntent" {
                restorationHandler(rootViewController!.viewControllers)
                return true
         }
        return false
    }

场景代理:

import UIKit
import Intents


class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    let app = UIApplication.shared.delegate as! AppDelegate
    var rootVC: UINavigationController?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
        rootVC = self.window?.rootViewController as? UINavigationController
        app.rootViewController = rootVC
    }

    func scene(_ scene: UIScene, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
        if userActivity.activityType == "VorratAddIntent" {
                app.rootViewController = rootVC
                restorationHandler(rootVC!.viewControllers)
                return true
        }
        return false
    }

有人知道我做错了什么吗?非常感谢您的支持。

我认为您缺少一种方法实现“确认”

func confirm(intent: VorratAddIntent, completion: @escaping (VorratAddIntentResponse) -> Void) {
    completion(.init(code: .success, userActivity: nil))
}

编辑:

此处documentation用于确认意向详情

编辑 2:

您必须在“句柄”方法中调用完成处理程序

 func handle(intent: VorratAddIntent, completion: @escaping (VorratAddIntentResponse) -> Void) {
    NotificationCenter.default.post(name: NSNotification.Name(rawValue: "NewVorratInputFromSiri"), object: intent)
    completion(your response)
}

编辑 3 - 将数据从扩展程序 (Siri) 传递到应用:

NotificationCenter 在扩展程序和主应用程序之间不起作用,因为它们没有以任何方式连接,这是完全不同的过程。

有不同的方法可以实现:

  • App Group:
    Here 是核心数据和扩展的示例,您可以如何共享数据库

  • UserDefaults with AppGroup:
    您也可以使用 UserDefaults(suiteName: your app group name)

  • NSUserActivity:
    SiriKit 最方便的方法 apple doc

NSUserActivity 示例

您将在 handle 方法中创建 NSUserActivity 实例,如下所示:

func handle(intent: VorratAddIntent, completion: @escaping (VorratAddIntentResponse) -> Void) {
     let response = VorratAddIntentResponse.success()

     let activity = NSUserActivity(activityType:NSStringFromClass(VorratAddIntent.self))
     activity.addUserInfoEntries(from: [
        "task_id" : your task id
     ])
     response.userActivity = activity

     completion(response)
}

而且你可以在你的应用程序中处理用户信息,如果应用程序已经 运行 你提取 task_id 值如下:

 func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
        guard userActivity.interaction?.intent is VorratAddIntent else { return }
        #if DEBUG
        print("Siri Intent user info: \(String(describing: userActivity.userInfo))")
        #endif
        guard let taskID = userActivity.userInfo?["task_id"] as? String
        else { return }
    }  

但是如果你的应用不在后台运行,你需要在场景中处理用户活动(willConnectTo:)

 func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
    
        for activity in connectionOptions.userActivities {
            self.scene(scene, continue: activity)
        }
    }