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

Posted

技术标签:

【中文标题】如何管理 Siri 意图启动我的应用程序并将数据传输到应用程序?【英文标题】:How to manage that Siri intent launches my app and transfers data to the app? 【发布时间】:2021-02-12 14:57:29 【问题描述】:

经过数小时的搜索,我请求您的帮助。

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

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

“Vorrat Add”无法执行。任务需要很长时间才能关闭。 重试”

我的设置如下:

我的意图处理程序 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
    

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

【问题讨论】:

【参考方案1】:

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

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

编辑:

这里是documentation,用于确认意图详细信息

编辑 2:

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

 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 是 Core Data 和 Extension 如何共享数据库的示例

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)
        
    

【讨论】:

非常感谢 - 这让我更进一步,Siri 对话现在以 .success 的确认屏幕结束。但是,在确认屏幕中按“完成”后,我的应用程序都不会启动,也不会通过通知传输意图数据。你知道这是什么原因吗?我把句柄函数作为完成:completion(.init(code: .success, userActivity: nil)) 我编辑了答案,进一步说明了如何在扩展程序和您的应用程序之间共享数据。

以上是关于如何管理 Siri 意图启动我的应用程序并将数据传输到应用程序?的主要内容,如果未能解决你的问题,请参考以下文章

如何正确使用文件映射并将数据传递给子进程?

如何自定义有关“failureRequiringAppLaunch”类型的 Siri 意图响应的错误消息

当我的应用程序通过我的 UITabBar 完成启动时,如何将加载的数据传递给 UITableView?

如何在 iOS 中检索数据并将数据传递给标签栏控制器的子视图?

导航架构组件 - 将参数数据传递给startDestination

如何在不捐赠意图或使用快捷方式的情况下利用 Siri 创建自定义对象?