iOS Swift 5 - 如何实现 Siri VoIP 命令“使用示例应用程序呼叫人员”支持,而无需添加快捷方式?

Lex*_*ers 6 voip shortcut ios siri swift

目标

我希望能够通过 Siri 说“使用 Next 应用程序呼叫 Emily”来触发 VoIP 呼叫,而无需添加快捷方式。

Emily 是我添加到我的联系人中的联系人,其中包含我自己的电话号码。我在日常司机手机上测试了示例应用程序,因此它有一张用于通话的 SIM 卡。

问题细分

  • 1 种场景有效:如果应用程序的捆绑(显示)名称与我所说的相同,例如“下一步”,Siri 将正确打开应用程序并启动呼叫代码,太棒了!
  • 但是,应用程序包名称必须保留 Nextext。
  • 如果我启动应用程序名称为 Nexxt 的应用程序并说“使用 Next 应用程序呼叫 Emily”,Siri 将回复:

我在你的通讯录中没有看到“Emily”。

  • 我添加了替代应用程序名称,即 CFBundleSpokenName,又名 Accessibility Bundle Name 和 INAlternativeAppNames:
    • 下一个
  • 替代应用程序名称有效!如果我说“打开下一个应用程序”、“打开狗应用程序”或“打开猫应用程序”,即使实际应用程序名称是 Nexxt,该应用程序也会打开。
  • 但是,如果我说“使用 Dog 应用程序呼叫 Emily”,Siri 会回复:

我没有看到这样的应用程序。您需要下载一个。搜索应用商店

  • 我似乎无法让它实现我的特定目标。考虑到替代应用程序名称的工作原理,我认为我的“INStartCallIntent”实现中可能存在根本性错误或缺失。

请注意,如果您在更改显示名称时遇到问题。选择项目,更改显示名称,然后单击项目之外的位置(任何文件)并再次选择项目。运行应用程序,名称应该会更新。

代码

这是代码。如果我的显示名称是下一个,它适用于“使用下一个应用程序呼叫艾米丽”。如果我的显示名称是 Dog,它也适用于“使用 Dog 应用程序呼叫 Emily”。

该示例应用程序是用 SwiftUI 代码编写的,只需进行最少的设置即可测试 Siri 功能。

TestSiriSimple -> TestSiriSimpleIntents -> IntentHandler:

import Intents

class IntentHandler: INExtension {
    
    override func handler(for intent: INIntent) -> Any {
        if intent is INStartCallIntent {
            return StartCallIntentHandler()
        }

        return self
    }
}
Run Code Online (Sandbox Code Playgroud)

TestSiriSimple -> 共享 -> StartCallIntentHandler:

import Foundation
import Intents

class StartCallIntentHandler: NSObject, INStartCallIntentHandling {
    
    func confirm(intent: INStartCallIntent) async -> INStartCallIntentResponse {
        let userActivity = NSUserActivity(activityType: String(describing: INStartCallIntent.self))
        return INStartCallIntentResponse(code: .continueInApp, userActivity: userActivity)
    }

    func handle(intent: INStartCallIntent, completion: @escaping (INStartCallIntentResponse) -> Void) {
        let response: INStartCallIntentResponse
        defer {
            completion(response)
        }
        
        let userActivity = NSUserActivity(activityType: String(describing: INStartCallIntent.self))
        response = INStartCallIntentResponse(code: .continueInApp, userActivity: userActivity)
        completion(response)
    }
    
    func resolveContacts(for intent: INStartCallIntent) async -> [INStartCallContactResolutionResult] {
        guard let contacts = intent.contacts, contacts.count > 0 else {
            return []
        }
        
        return [INStartCallContactResolutionResult.success(with: contacts[0])]
    }
    
    func resolveCallCapability(for intent: INStartCallIntent) async -> INStartCallCallCapabilityResolutionResult {
        INStartCallCallCapabilityResolutionResult(callCapabilityResolutionResult: .success(with: intent.callCapability))
    }
    
    func resolveDestinationType(for intent: INStartCallIntent) async -> INCallDestinationTypeResolutionResult {
        INCallDestinationTypeResolutionResult.success(with: .normal)
    }
}
Run Code Online (Sandbox Code Playgroud)

根应用程序类未更改。TestSiriSimple -> 共享 -> ContentView:

import SwiftUI
import Intents

struct ContentView: View {
    @State private var status: INSiriAuthorizationStatus = .notDetermined
    
    var body: some View {
        Text("Hello, world! Siri status: \(status.readableDescription)")
            .padding()
            .onAppear {
                requestSiri()
            }
            .onContinueUserActivity(NSStringFromClass(INStartCallIntent.self)) { userActivity in
                continueUserActivity(userActivity)
            }
    }
    
    private func requestSiri() {
        INPreferences.requestSiriAuthorization { status in
            self.status = status
        }
    }
    
    private func continueUserActivity(_ userActivity: NSUserActivity) {
        if let intent = userActivity.interaction?.intent as? INStartCallIntent {
            // Find person from contacts or create INPerson from app specific contacts.
            // Execute VoIP code.
            // I consider it a success if Siri responds with "Calling Now", opens the app and reaches this code.
        }
    }
}

extension INSiriAuthorizationStatus {
    var readableDescription: String {
        switch self {
        case .authorized:
            return "Authorized"
        case .denied:
            return "Denied"
        case .notDetermined:
            return "Not determined"
        case .restricted:
            return "Restricted"
        default:
            return "Unknown"
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
细节

TestSiriSimple ->(主)Info.plist

TestSiriSimpleIntents -> Info.plist

隐私 - Siri 使用说明 = Siri 希望让您在此应用程序中发起通话。

TestSiriSimpleIntents 目标将 INStartCallIntent 作为受支持的意图

如果您有任何想法,我们非常欢迎!

如果您能向我展示如何在 StackOverflow 中实现这一点,我愿意分享我的示例代码的 zip 文件。如果任何其他信息有帮助,请随时发表评论!

Lex*_*ers 1

有一个比 更好的短语Call Emily using the Nexxt app,请尝试以下操作:

Call Emily on Nexxt
Run Code Online (Sandbox Code Playgroud)

请参阅文档: https ://developer.apple.com/documentation/sirikit/instartaudiocallintent

示例短语