CtrlK
BlogDocsLog inGet started
Tessl Logo

thiennc-tesoglobal/ios-skills

Community-maintained Agent Skills for complete Swift and Apple-platform app delivery.

73

Quality

92%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Medium

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

assistant-focus-and-intent-behavior.mdskills/app-intents/references/

App Intents Assistant, Focus, and Intent Behavior

Read this reference only when the task matches the sections below.

Assistant Schemas (iOS 18+)

Assistant schemas define domain-specific intents that Apple Intelligence understands natively. Annotate conforming types with schema macros.

Declaration

// Preferred macro (iOS 18+)
@AppIntent(schema: .photos.openAsset)
struct OpenPhotoIntent: AppIntent { ... }

// CORRECT: Using preferred macro
@AppIntent(schema: .photos.openAsset)
struct OpenPhotoIntent: AppIntent {
    static var title: LocalizedStringResource = "Open Photo"

    @Parameter(title: "Asset")
    var target: PhotoEntity

    func perform() async throws -> some IntentResult {
        PhotoViewer.shared.open(target.id)
        return .result()
    }
}

@AppEntity(schema: .photos.asset)
struct PhotoEntity: AppEntity {
    var id: String
    static let defaultQuery = PhotoQuery()
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Photo"
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(title: "\(name)")
    }
    var name: String
}

@AppEnum(schema: .photos.assetType)
enum PhotoType: String, AppEnum {
    case photo, video, livePhoto
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Photo Type"
    static var caseDisplayRepresentations: [PhotoType: DisplayRepresentation] = [
        .photo: "Photo",
        .video: "Video",
        .livePhoto: "Live Photo"
    ]
}

Avoid the deprecated AssistantIntent(schema:), AssistantEntity(schema:), and AssistantEnum(schema:) macros in new code.

Domain catalog

Use Xcode completion and the current domain docs for exact schema cases. The major Apple domains are:

DomainExample actionsExample content
Assistantside-button conversational app launch--
Booksopen book, create bookmarkbook, audiobook
Browseropen tab, create bookmark, search webtab, bookmark, window
Cameracapture photo, capture video--
File managementopen, create, move, rename, delete filefile
Journalingcreate, update, delete, search entryjournal entry
Mailopen mailbox, send draftaccount, draft, mailbox, message
Photosopen asset, create album, search assetsalbum, asset, person
Presentationsopen document, add slidedocument, slide, template
Readeropen document, go to pagedocument, page
Spreadsheetopen document, add sheetdocument, sheet, template
System and in-app searchsearch--
Visual intelligencesemantic content search--
Whiteboardopen board, create itemboard, item
Word processoropen document, add pagedocument, page, template

isAssistantOnly

Control whether a schema-conforming type is exclusive to Apple Intelligence or also available through other system surfaces:

@AppIntent(schema: .photos.openAsset)
struct OpenPhotoIntent: AppIntent {
    static let isAssistantOnly = false  // Also available in Shortcuts
    // ...
}

Focus Filter Intents

Customize app behavior when a Focus mode activates.

struct WorkFocusFilter: SetFocusFilterIntent {
    static var title: LocalizedStringResource = "Work Focus"
    static var description = IntentDescription("Configure app for work mode.")

    @Parameter(title: "Show Only Work Projects", default: true)
    var workOnly: Bool

    @Parameter(title: "Mute Notifications", default: false)
    var muteNotifications: Bool

    var displayRepresentation: DisplayRepresentation {
        "Work Mode"
    }

    func perform() async throws -> some IntentResult {
        AppSettings.shared.workModeEnabled = workOnly
        AppSettings.shared.notificationsMuted = muteNotifications
        return .result()
    }
}

Access current focus filter

let currentFilter = try? SetFocusFilterIntent.current
if let workFilter = currentFilter as? WorkFocusFilter {
    // Apply work-mode behavior
}

Suggest filters for a focus context

extension WorkFocusFilter {
    static func suggestedFocusFilters(
        for context: FocusFilterSuggestionContext
    ) async -> [WorkFocusFilter] {
        [WorkFocusFilter(workOnly: true, muteNotifications: true)]
    }
}

SiriKit Migration (CustomIntentMigratedAppIntent)

Replace SiriKit custom intents (.intentdefinition files) while preserving existing user shortcuts and donations.

struct OrderSoupIntent: CustomIntentMigratedAppIntent {
    // Map to the old SiriKit intent class name -- must match exactly
    static var intentClassName: String = "OrderSoupIntent"

    static var title: LocalizedStringResource = "Order Soup"

    @Parameter(title: "Soup")
    var soup: SoupEntity

    @Parameter(title: "Quantity", default: 1)
    var quantity: Int

    func perform() async throws -> some IntentResult {
        let order = try await OrderService.shared.place(
            soup: soup.id,
            quantity: quantity
        )
        return .result(dialog: "Ordered \(quantity) bowls.")
    }
}

Migration steps

  1. Create a new AppIntent struct conforming to CustomIntentMigratedAppIntent.
  2. Set intentClassName to the old SiriKit intent class name (exact match).
  3. Recreate parameters using @Parameter instead of .intentdefinition props.
  4. Implement perform() with async/await.
  5. Existing user shortcuts and donations continue working via the class name.
  6. Remove the .intentdefinition file once migration is verified.

DeprecatedAppIntent (versioning within AppIntents)

Replace an old AppIntent with a newer version:

struct OldSearchIntent: DeprecatedAppIntent {
    typealias ReplacementIntent = NewSearchIntent
    static var deprecation: IntentDeprecation {
        .init(message: "Use the new search intent.")
    }
    static var title: LocalizedStringResource = "Search (Deprecated)"
    func perform() async throws -> some IntentResult { .result() }
}

Error Handling and Dialog

Standard error types (iOS 18+)

func perform() async throws -> some IntentResult {
    guard await PermissionManager.hasPhotoAccess else {
        throw AppIntentError.PermissionRequired.photos
    }

    guard let item = try await fetchItem() else {
        throw AppIntentError.Unrecoverable.entityNotFound
    }

    guard !requiresManualSetup else {
        throw AppIntentError.UserActionRequired.accountSetup
    }

    return .result()
}
Error TypeWhen to Use
AppIntentError.PermissionRequiredMissing OS-level permission
AppIntentError.UnrecoverableFatal state with no immediate remedy
AppIntentError.UserActionRequiredUser must sign in, confirm, or set up an account

Parameter-level errors

// Re-prompt for a value
throw $quantity.needsValueError("How many items?")

// Force disambiguation
throw $size.needsDisambiguation(among: [.small, .medium, .large])

Foreground continuation

func perform() async throws -> some IntentResult {
    if needsUserInteraction {
        try await continueInForeground("Open the app to finish.")
    }
    // ...
    return .result()
}

Dialog in results

func perform() async throws -> some IntentResult & ProvidesDialog {
    return .result(dialog: "Your soup order has been placed.")
}

func perform() async throws -> some IntentResult & ProvidesDialog & ReturnsValue<OrderEntity> {
    let order = try await placeOrder()
    return .result(
        value: OrderEntity(from: order),
        dialog: "Order #\(order.number) is confirmed."
    )
}

Confirmation Flows

Basic confirmation

func perform() async throws -> some IntentResult {
    try await requestConfirmation(
        actionName: .send,
        dialog: "Send \(quantity) messages?"
    )
    // User confirmed -- proceed
    return .result()
}

Conditional confirmation

func perform() async throws -> some IntentResult {
    try await requestConfirmation(
        conditions: .always,
        actionName: .order,
        dialog: "Place order for \(quantity) \(soup.name)?"
    )
    return .result()
}

Confirmation with SwiftUI content

func perform() async throws -> some IntentResult {
    try await requestConfirmation(
        actionName: .buy,
        dialog: "Purchase \(item.name) for \(item.price)?",
        view: OrderPreviewView(item: item)
    )
    return .result()
}

User choice

func perform() async throws -> some IntentResult {
    let chosen = try await requestChoice(
        between: availableOptions,
        dialog: "Which option would you like?"
    )
    // Use chosen value
    return .result()
}

ConfirmationActionName options

Built-in: .add, .buy, .call, .create, .send, .share, .start, .toggle, .turnOn, .turnOff, .open, .play, .post, .search, .book, .download, .pay, .order, .run, .get, .go, .log, .set, .view, .find, .filter, .continue, .do, .addData, .checkIn, .request, .playSound, .startNavigation.

Custom:

.custom(
    acceptLabel: "Confirm Purchase",
    acceptAlternatives: ["Yes", "Buy it"],
    denyLabel: "Cancel",
    denyAlternatives: ["No", "Never mind"],
    destructive: false
)

Authentication Policies

Control when device authentication is required:

struct TransferMoneyIntent: AppIntent {
    static var authenticationPolicy: IntentAuthenticationPolicy = .requiresAuthentication
    static var title: LocalizedStringResource = "Transfer Money"

    func perform() async throws -> some IntentResult {
        // Device must be unlocked before this runs
        return .result()
    }
}
PolicyBehavior
.alwaysAllowedNo authentication required
.requiresAuthenticationDevice must be unlocked
.requiresLocalDeviceAuthenticationFace ID / Touch ID required
// WRONG: Sensitive action without authentication
struct DeleteAccountIntent: AppIntent {
    // Missing authenticationPolicy -- runs on locked device
    func perform() async throws -> some IntentResult { ... }
}

// CORRECT: Require authentication for sensitive actions
struct DeleteAccountIntent: AppIntent {
    static var authenticationPolicy: IntentAuthenticationPolicy = .requiresLocalDeviceAuthentication
    static var title: LocalizedStringResource = "Delete Account"
    func perform() async throws -> some IntentResult { ... }
}

skills

.mcp.json

README.md

tile.json