Siri 集成:使用 Siri 进行音频通话?

Siri Integration: Audio call using Siri?

我需要集成 Siri 才能使用我的应用拨打电话。 Siri 在设置的应用程序支持上识别我的应用程序。但是当我试图打电话给某个联系人说 "call ContactName on MyApp" 时,它只显示按钮 "open MyApp"。 IntentHandler.swift 包含此函数:

class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling, INStartAudioCallIntentHandling {

    override func handler(for intent: INIntent) -> Any {
        // This is the default implementation.  If you want different objects to handle different intents,
        // you can override this and return the handler you want for that particular intent.

        return self
    }

    func handle(startAudioCall intent: INStartAudioCallIntent, completion: @escaping (INStartAudioCallIntentResponse) -> Swift.Void) {
        let response: INStartAudioCallIntentResponse
        defer {
            completion(response)
        }

        let contacts = (intent.contacts ?? []).filter({ (contact) -> Bool in
            return contact.personHandle?.type == .phoneNumber && contact.personHandle?.value != nil
        })
        // Ensure there is a contact and a handle
        guard contacts.count > 0 else {
            response = INStartAudioCallIntentResponse(code: .failure, userActivity: nil)
            return
        }

        let userActivity = NSUserActivity(activityType: "INStartAudioCallIntent")

        response = INStartAudioCallIntentResponse(code: .continueInApp, userActivity: userActivity)
        completion(response)

    }

    func resolveContacts(forStartAudioCall intent: INStartAudioCallIntent, with completion: @escaping ([INPersonResolutionResult]) -> Void) {
        var resolutionResults = [INPersonResolutionResult]()

        if let recipients = intent.contacts {
            if recipients.count == 0 {
                let response = [INPersonResolutionResult.needsValue()]
                completion(response)
                return
            } else if recipients.count == 1 {
                if let recipient = recipients.first {
                    if self.containContact(displayName: (recipient.displayName)) {
                        resolutionResults.append(INPersonResolutionResult.success(with: recipient))
                    } else {
                        resolutionResults.append(INPersonResolutionResult.unsupported())
                    }
                }
            } else if recipients.count > 1 {
                resolutionResults.append(INPersonResolutionResult.disambiguation(with: recipients))

            } else {
                resolutionResults.append(INPersonResolutionResult.unsupported())
            }
        }
        completion(resolutionResults)
    }
    func confirm(startAudioCall intent: INStartAudioCallIntent, completion: @escaping (INStartAudioCallIntentResponse) -> Swift.Void) {
        let userActivity = NSUserActivity(activityType: "INStartAudioCallIntent")
        let response = INStartAudioCallIntentResponse(code: .ready, userActivity: userActivity)
        completion(response)

    }

    // MARK: - INSendMessageIntentHandling

    // Implement resolution methods to provide additional information about your intent (optional).


    func resolveRecipients(forSendMessage intent: INSendMessageIntent, with completion: @escaping ([INPersonResolutionResult]) -> Void) {
        if let recipients = intent.recipients {

            // If no recipients were provided we'll need to prompt for a value.
            if recipients.count == 0 {
                completion([INPersonResolutionResult.needsValue()])
                return
            }

            var resolutionResults = [INPersonResolutionResult]()
            for recipient in recipients {
                let matchingContacts = [recipient] // Implement your contact matching logic here to create an array of matching contacts
                switch matchingContacts.count {
                case 2  ... Int.max:
                    // We need Siri's help to ask user to pick one from the matches.
                    resolutionResults += [INPersonResolutionResult.disambiguation(with: matchingContacts)]

                case 1:
                    // We have exactly one matching contact
                    resolutionResults += [INPersonResolutionResult.success(with: recipient)]

                case 0:
                    // We have no contacts matching the description provided
                    resolutionResults += [INPersonResolutionResult.unsupported()]

                default:
                    break

                }
            }
            completion(resolutionResults)
        }
    }

    func resolveContent(forSendMessage intent: INSendMessageIntent, with completion: @escaping (INStringResolutionResult) -> Void) {
        if let text = intent.content, !text.isEmpty {
            completion(INStringResolutionResult.success(with: text))
        } else {
            completion(INStringResolutionResult.needsValue())
        }
    }

    // Once resolution is completed, perform validation on the intent and provide confirmation (optional).

    func confirm(sendMessage intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
        // Verify user is authenticated and your app is ready to send a message.

        let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
        let response = INSendMessageIntentResponse(code: .ready, userActivity: userActivity)
        completion(response)
    }

    // Handle the completed intent (required).

    func handle(sendMessage intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
        // Implement your application logic to send a message here.

        let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
        let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)
        completion(response)
    }

    // Implement handlers for each intent you wish to handle.  As an example for messages, you may wish to also handle searchForMessages and setMessageAttributes.

    // MARK: - INSearchForMessagesIntentHandling

    func handle(searchForMessages intent: INSearchForMessagesIntent, completion: @escaping (INSearchForMessagesIntentResponse) -> Void) {
        // Implement your application logic to find a message that matches the information in the intent.

        let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
        let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity)
        // Initialize with found message's attributes
        response.messages = [INMessage(
            identifier: "identifier",
            content: "I am so excited about SiriKit!",
            dateSent: Date(),
            sender: INPerson(personHandle: INPersonHandle(value: "sarah@example.com", type: .emailAddress), nameComponents: nil, displayName: "Sarah", image: nil,  contactIdentifier: nil, customIdentifier: nil),
            recipients: [INPerson(personHandle: INPersonHandle(value: "+1-415-555-5555", type: .phoneNumber), nameComponents: nil, displayName: "John", image: nil,  contactIdentifier: nil, customIdentifier: nil)]
            )]
        completion(response)
    }

    // MARK: - INSetMessageAttributeIntentHandling

    func handle(setMessageAttribute intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) {
        // Implement your application logic to set the message attribute here.

        let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
        let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity)
        completion(response)
    }

    func containContact(displayName: String) -> Bool {
        //fetch contacts and check, if exist retun YES else NO
        return true
    }
}
}

        let contacts = (intent.contacts ?? []).filter({ (contact) -> Bool in
            return contact.personHandle?.type == .phoneNumber && contact.personHandle?.value != nil
        })
        // Ensure there is a contact and a handle
        guard contacts.count > 0 else {
            response = INStartAudioCallIntentResponse(code: .failure, userActivity: nil)
            return
        }

        let userActivity = NSUserActivity(activityType: "INStartAudioCallIntent")

        response = INStartAudioCallIntentResponse(code: .continueInApp, userActivity: userActivity)

    }

我做错了什么?我错过了什么?

请检查以下内容:

在扩展的目标中为 IntentsSupported 设置 INStartAudioCallIntent。

确认默认提供的IntentHandlerclass中的INStartAudioCallIntentHandling协议,实现如下方法:

-(void)resolveContactsForStartAudioCall:(INStartAudioCallIntent *)intent
                          withCompletion:(void (^)(NSArray<INPersonResolutionResult *> *resolutionResults))completion{
    NSArray<INPerson *> *recipients = intent.contacts;
    NSMutableArray<INPersonResolutionResult *> *resolutionResults = [NSMutableArray array];
    if (recipients.count == 0) {
        completion(@[[INPersonResolutionResult needsValue]]);
        return;
    }else if(recipients.count==1){
        if ([self contactExist:recipients.firstObject.displayName]) {// check if person contains in contact or not
            [resolutionResults addObject:[INPersonResolutionResult successWithResolvedPerson:recipients.firstObject]];
        }else [resolutionResults addObject:[INPersonResolutionResult unsupported]];
    }else if(recipients.count>1){
        [resolutionResults addObject:[INPersonResolutionResult disambiguationWithPeopleToDisambiguate:recipients]];
    }else{
        [resolutionResults addObject:[INPersonResolutionResult unsupported]];
    }
    completion(resolutionResults);
}

-(void)confirmStartAudioCall:(INStartAudioCallIntent *)intent
                   completion:(void (^)(INStartAudioCallIntentResponse *response))completion{
    NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:NSStringFromClass([INStartAudioCallIntent class])];
    INStartAudioCallIntentResponse *response = [[INStartAudioCallIntentResponse alloc] initWithCode:INStartAudioCallIntentResponseCodeReady userActivity:userActivity];
    completion(response);
}

-(void)handleStartAudioCall:(INStartAudioCallIntent *)intent
                  completion:(void (^)(INStartAudioCallIntentResponse *response))completion{
    NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:NSStringFromClass([INStartAudioCallIntent class])];
    INStartAudioCallIntentResponse *response = [[INStartAudioCallIntentResponse alloc] initWithCode:INStartAudioCallIntentResponseCodeContinueInApp userActivity:userActivity];
    completion(response);
}

您可能需要与扩展共享主应用程序的联系人,使用应用程序分组。关注apple document获取更多信息。

确保扩展目标支持您的设备OS。