Skip to content
All essays
iOSMarch 27, 202511 min

Swift SiriKit: Voice Assistant Integration

Integrate your app with Siri. Voice commands, intents, and custom responses.

Ü
Ümit Uz
Mobile & Full Stack Developer

SiriKit enables your app to work with Siri using voice commands. Learn to implement intents and custom responses.

SiriKit Setup

Add Siri Capability

  1. 1Go to Project Settings > Signing & Capabilities
  2. 2Add "Siri" capability
  3. 3Define intents in Intent Definition file

Intent Extension

Create Intent Extension

swift
import Intents

class SendMessageIntentHandler: NSObject, SendMessageIntentHandling {
    func handle(intent: SendMessageIntent, completion: @escaping (SendMessageIntentResponse) -> Void) {
        guard let recipient = intent.recipient,
              let message = intent.message else {
            completion(MessageSendIntentResponse(code: .failure, userActivity: nil))
            return
        }

        // Send message logic
        sendMessage(to: recipient, message: message) { success in
            if success {
                let response = SendMessageIntentResponse.success(userActivity: nil)
                completion(response)
            } else {
                completion(MessageSendIntentResponse.failure(failureError: nil, userActivity: nil))
            }
        }
    }

    func resolveRecipient(for intent: SendMessageIntent, with completion: @escaping (INSpeakableStringResolutionResult) -> Void) {
        if let recipient = intent.recipient {
            completion(.success(with: recipient))
        } else {
            completion(.disambiguation(with: [], needsValue: nil))
        }
    }

    func resolveMessage(for intent: SendMessageIntent, with completion: @escaping (INStringResolutionResult) -> Void) {
        if let message = intent.message {
            completion(.success(with: message))
        } else {
            completion(.needsValue())
        }
    }

    private func sendMessage(to recipient: String, message: String, completion: @escaping (Bool) -> Void) {
        // Message sending logic
        completion(true)
    }
}

Custom Intents

Define Intent

swift
// Intent Definition File
// Intent Definition: Start Workout

import Intents

@objc(StartWorkoutIntent)
public class StartWorkoutIntent: NSObject, INIntent {
    @objc public var workoutType: INSpeakableString?
    @objc public var duration: NSNumber?

    @NSManaged public var suggestedInvocationPhrase: String?

    convenience init(workoutType: String, duration: Int) {
        self.init()
        self.workoutType = INSpeakableString(spokenPhrase: workoutType)
        self.duration = NSNumber(value: duration)
    }
}

// Intent Handler
class StartWorkoutIntentHandler: NSObject, StartWorkoutIntentHandling {
    func handle(intent: StartWorkoutIntent, completion: @escaping (StartWorkoutIntentResponse) -> Void) {
        guard let workoutType = intent.workoutType?.spokenPhrase,
              let duration = intent.duration?.intValue else {
            completion(StartWorkoutIntentResponse(code: .failure, userActivity: nil))
            return
        }

        // Start workout
        startWorkout(type: workoutType, duration: duration) { success in
            if success {
                let response = StartWorkoutIntentResponse.success(userActivity: nil)
                completion(response)
            } else {
                completion(StartWorkoutIntentResponse.failure(failureError: nil, userActivity: nil))
            }
        }
    }

    func resolveWorkoutType(for intent: StartWorkoutIntent, with completion: @escaping (INSpeakableStringResolutionResult) -> Void) {
        if let workoutType = intent.workoutType {
            completion(.success(with: workoutType))
        } else {
            completion(.disambiguation(with: ["Running", "Cycling", "Swimming"], needsValue: nil))
        }
    }

    func resolveDuration(for intent: StartWorkoutIntent, with completion: @escaping (INIntegerResolutionResult) -> Void) {
        if let duration = intent.duration {
            completion(.success(with: duration))
        } else {
            completion(.needsValue())
        }
    }

    private func startWorkout(type: String, duration: Int, completion: @escaping (Bool) -> Void) {
        // Workout start logic
        completion(true)
    }
}

Intent UI

Custom Intent UI

swift
class WorkoutIntentViewController: UIViewController, INUIHostedViewControllerControlling {
    func configureView(for intent: StartWorkoutIntent, with context: INIntentHostViewContext, completion: @escaping (CGSize, INUIHostedViewControllerDesiredMetrics) -> Void) {
        let desiredSize = CGSize(width: 320, height: 100)
        completion(desiredSize, .full)
    }

    func configureViewForInteraction(with intent: StartWorkoutIntent, context: INIntentHostViewContext, interaction: INUIHostedViewInteraction) {
        // Configure interaction
    }
}

Siri Shortcuts

swift
import Intents

class ShortcutDonationManager {
    func donateWorkoutShortcut(workoutType: String, duration: Int) {
        let intent = StartWorkoutIntent(workoutType: workoutType, duration: duration)

        let interaction = INInteraction(intent: intent, response: nil)

        interaction.donate { error in
            if let error = error {
                print("Error donating shortcut: \(error)")
            } else {
                print("Shortcut donated successfully")
            }
        }
    }

    func donateSendMessageShortcut(to recipient: String) {
        let intent = SendMessageIntent()
        intent.recipient = INPerson(handle: recipient)
        intent.message = "Test message"

        let interaction = INInteraction(intent: intent, response: nil)
        interaction.donate { error in
            if let error = error {
                print("Error donating shortcut: \(error)")
            }
        }
    }
}

INAppAccount

Account Integration

swift
class AppAccountManager {
    func saveAccount(username: String) {
        let account = INAppAccount(
            accountName: INSpeakableString(spokenPhrase: username)
        )

        let accountInteraction = INInteraction(account: account)
        accountInteraction.donate { error in
            if let error = error {
                print("Error donating account: \(error)")
            }
        }
    }

    func removeAccount(username: String) {
        // Remove account logic
    }
}

Siri Suggestions

Enable Suggestions

swift
class SiriSuggestionsManager {
    func donateSearchResult(query: String) {
        let intent = INSearchForItemsIntent()
        intent.searchQuery = query

        let response = INSearchForItemsIntentResponse(
            searchedItems: [],
            searchResults: [],
            userActivity: NSUserActivity(activityType: "com.yourapp.search")
        )

        let interaction = INInteraction(intent: intent, response: response)
        interaction.donate { error in
            if let error = error {
                print("Error donating suggestion: \(error)")
            }
        }
    }
}

Voice Shortcuts

Custom Phrases

swift
extension ShortcutDonationManager {
    func donateCustomPhrase(phrase: String, intent: INIntent) {
        let interaction = INInteraction(intent: intent, response: nil)

        interaction.donation {
            donationError in
            if let donationError = donationError {
                print("Error donating phrase: \(donationError)")
            } else {
                print("Phrase donated: \(phrase)")
            }
        }
    }
}

Best Practices

  1. 1Phrases: Use natural phrases
  2. 2Testing: Test voice commands thoroughly
  3. 3Responses: Provide clear responses
  4. 4Fallback: Handle failures gracefully
  5. 5Consistency: Keep responses consistent
  6. 6Context: Maintain context across interactions
  7. 7Performance: Optimize intent handling
  8. 8User Experience: Focus on user experience

SiriKit enables powerful voice control in your apps!

Related essays

Next essay
Swift Core Bluetooth: LE Device Integration