Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
71
89%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Extended App Intents patterns beyond the basics covered in the main skill.
Covers @Parameter variants, EntityPropertyQuery, assistant schemas, focus
filters, SiriKit migration, error handling, confirmation flows, authentication,
URL-representable types, and Spotlight indexing.
@Parameter Initializer Variants@ComputedProperty(indexingKey:) for Spotlight (iOS 26+)@Parameter Initializer Variants@Parameter(title: "Name")
var name: String
@Parameter(title: "Name", description: "The user's full name")
var name: String
@Parameter(title: "Enabled", default: true)
var enabled: Bool
@Parameter(title: "Website")
var url: URL?@Parameter(title: "Volume", controlStyle: .slider, inclusiveRange: (0, 100))
var volume: Int
@Parameter(title: "Rating", controlStyle: .stepper, inclusiveRange: (1, 5))
var rating: Int
@Parameter(title: "Temperature", default: 72.0, inclusiveRange: (60.0, 90.0))
var temperature: DoubleControl styles: .field (default text field), .slider, .stepper.
Provide a dynamic set of options at runtime:
struct CategoryOptionsProvider: DynamicOptionsProvider {
func results() async throws -> [String] {
await CategoryStore.shared.allNames()
}
}
@Parameter(title: "Category", optionsProvider: CategoryOptionsProvider())
var category: StringRequest clarification when the system cannot resolve a value:
@Parameter(
title: "Size",
requestValueDialog: "What size would you like?",
requestDisambiguationDialog: "Which size did you mean?"
)
var size: CupSizeTransform raw input into the target type:
@Parameter(title: "Contact", resolvers: [ContactResolver()])
var contact: ContactEntitySpecify a custom query for entity resolution:
@Parameter(title: "Trail", query: [TrailStringQuery(), TrailPropertyQuery()])
var trail: TrailEntity// Fixed size
@Parameter(title: "Items", size: 3)
var items: [ItemEntity]
// Per-widget-family size
@Parameter(
title: "Items",
size: [.systemSmall: 1, .systemMedium: 3, .systemLarge: 6]
)
var items: [ItemEntity]@Parameter(title: "Document", supportedContentTypes: [.pdf, .plainText])
var document: IntentFile
@Parameter(title: "Image", supportedContentTypes: [.png, .jpeg])
var image: IntentFile?@Parameter(
title: "Distance",
defaultUnit: .miles,
defaultUnitAdjustForLocale: true,
supportsNegativeNumbers: false
)
var distance: Measurement<UnitLength>
@Parameter(title: "Weight", defaultUnit: .kilograms)
var weight: Measurement<UnitMass>
@Parameter(title: "Temperature", defaultUnit: .fahrenheit)
var temp: Measurement<UnitTemperature>Control how parameters connect to Shortcuts input:
@Parameter(title: "Text", inputConnectionBehavior: .connectToPreviousIntentResult)
var text: String
@Parameter(title: "File", inputConnectionBehavior: .optionalIfProvided)
var file: IntentFile?Request values, disambiguation, and confirmation at runtime inside perform():
func perform() async throws -> some IntentResult {
// Request a missing value
if quantity == nil {
throw $quantity.needsValueError("How many would you like?")
}
// Disambiguate among options
let resolved = try await $size.requestDisambiguation(
among: [.small, .medium, .large],
dialog: "Which size?"
)
// Confirm a value
try await $amount.requestConfirmation(for: amount, dialog: "Charge \(amount)?")
return .result()
}The most powerful query variant. Declare filterable properties and sortable fields for structured Siri and Shortcuts queries.
struct TrailPropertyQuery: EntityPropertyQuery {
static var properties = QueryProperties {
Property(\TrailEntity.$name) {
ContainsComparator()
EqualToComparator()
}
Property(\TrailEntity.$trailLength) {
GreaterThanComparator()
LessThanComparator()
EqualToComparator()
}
}
static var sortingOptions = SortingOptions {
SortableBy(\TrailEntity.$name)
SortableBy(\TrailEntity.$trailLength)
}
func entities(
matching comparators: [EntityQueryComparator],
mode: ComparatorMode,
sortedBy: [EntityQuerySort<TrailEntity>],
limit: Int?
) async throws -> [TrailEntity] {
var results = TrailStore.shared.allTrails.map { TrailEntity(from: $0) }
for comparator in comparators {
switch comparator {
case let comparator as ContainsComparator<String>:
results = results.filter { $0.name.localizedCaseInsensitiveContains(comparator.value) }
case let comparator as GreaterThanComparator<Measurement<UnitLength>>:
results = results.filter { $0.trailLength > comparator.value }
default:
break
}
}
if let limit {
results = Array(results.prefix(limit))
}
return results
}
func entities(for identifiers: [Trail.ID]) async throws -> [TrailEntity] {
TrailStore.shared.allTrails
.filter { identifiers.contains($0.id) }
.map { TrailEntity(from: $0) }
}
func suggestedEntities() async throws -> [TrailEntity] {
TrailStore.shared.featured.map { TrailEntity(from: $0) }
}
}| Comparator | Supported Types |
|---|---|
EqualToComparator | All comparable types |
NotEqualToComparator | All comparable types |
ContainsComparator | String |
HasPrefixComparator | String |
HasSuffixComparator | String |
GreaterThanComparator | Numeric, Date, Measurement |
LessThanComparator | Numeric, Date, Measurement |
GreaterThanOrEqualToComparator | Numeric, Date, Measurement |
LessThanOrEqualToComparator | Numeric, Date, Measurement |
IsBetweenComparator | Numeric, Date, Measurement |
Assistant schemas define domain-specific intents that Apple Intelligence understands natively. Annotate conforming types with schema macros.
// PREFERRED: Newer macro (iOS 18+)
@AppIntent(schema: .photos.openAsset)
struct OpenPhotoIntent: AppIntent { ... }
// ALSO VALID: @AssistantIntent(schema:) still works but @AppIntent(schema:) is preferred
@AssistantIntent(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"
]
}| Domain | Example Intents | Example Entities |
|---|---|---|
.books | openBook, createBookmark | book, audiobook |
.browser | openTab, createBookmark, searchWeb | tab, bookmark, window |
.camera | capturePhoto, captureVideo | -- |
.reader | openDocument, goToPage | document, page |
.files | openFile, createFile | file |
.assistant | activate | -- |
.journal | createEntry, openEntry | entry |
.mail | openMailbox, sendDraft | account, draft, mailbox, message |
.photos | openAsset, createAlbum, searchAssets | album, asset, recognizedPerson |
.presentation | openDocument, addSlide | document, slide, template |
.spreadsheet | openDocument, addSheet | document, sheet, template |
.system | search | -- |
.whiteboard | openBoard, createItem | board, item |
.wordProcessor | openDocument, addPage | document, page, template |
.visualIntelligence | semanticContentSearch | -- |
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 var isAssistantOnly: Bool { false } // Also available in Shortcuts
// ...
}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()
}
}let currentFilter = try? SetFocusFilterIntent.current
if let workFilter = currentFilter as? WorkFocusFilter {
// Apply work-mode behavior
}extension WorkFocusFilter {
static func suggestedFocusFilters(
for context: FocusFilterSuggestionContext
) async -> [WorkFocusFilter] {
[WorkFocusFilter(workOnly: true, muteNotifications: true)]
}
}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.")
}
}AppIntent struct conforming to CustomIntentMigratedAppIntent.intentClassName to the old SiriKit intent class name (exact match).@Parameter instead of .intentdefinition props.perform() with async/await..intentdefinition file once migration is verified.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() }
}func perform() async throws -> some IntentResult {
guard await PermissionManager.hasPhotoAccess else {
throw PermissionRequired(
"Photo library access is required.",
recoverySuggestion: "Grant access in Settings > Privacy."
)
}
guard let item = try await fetchItem() else {
throw Unrecoverable("The item no longer exists.")
}
guard !requiresManualSetup else {
throw UserActionRequired("Open the app to complete setup.")
}
return .result()
}| Error Type | When to Use |
|---|---|
PermissionRequired | Missing OS-level permission |
Unrecoverable | Fatal -- intent cannot proceed |
UserActionRequired | User must act in the app |
// Re-prompt for a value
throw $quantity.needsValueError("How many items?")
// Force disambiguation
throw $size.needsDisambiguation(among: [.small, .medium, .large])func perform() async throws -> some IntentResult {
if needsUserInteraction {
try await continueInForeground("Open the app to finish.")
}
// ...
return .result()
}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."
)
}func perform() async throws -> some IntentResult {
try await requestConfirmation(
actionName: .send,
dialog: "Send \(quantity) messages?"
)
// User confirmed -- proceed
return .result()
}func perform() async throws -> some IntentResult {
try await requestConfirmation(
conditions: .always,
actionName: .order,
dialog: "Place order for \(quantity) \(soup.name)?"
)
return .result()
}func perform() async throws -> some IntentResult {
try await requestConfirmation(
actionName: .buy,
dialog: "Purchase \(item.name) for \(item.price)?",
view: OrderPreviewView(item: item)
)
return .result()
}func perform() async throws -> some IntentResult {
let chosen = try await requestChoice(
between: availableOptions,
dialog: "Which option would you like?"
)
// Use chosen value
return .result()
}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
)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()
}
}| Policy | Behavior |
|---|---|
.alwaysAllowed | No authentication required |
.requiresAuthentication | Device must be unlocked |
.requiresLocalDeviceAuthentication | Face 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 { ... }
}Represent intents, entities, and enums as URLs for deep linking.
struct OpenRecipeIntent: URLRepresentableIntent {
static var title: LocalizedStringResource = "Open Recipe"
@Parameter(title: "Recipe")
var target: RecipeEntity
static var parameterSummary: some ParameterSummary {
Summary("Open \(\.$target)")
}
func perform() async throws -> some IntentResult & OpensIntent {
return .result()
}
}
extension OpenRecipeIntent {
static var urlRepresentation: some URLRepresentation {
URLRepresentation {
"https://myapp.com/recipes/\(\.$target)"
}
}
}struct RecipeEntity: URLRepresentableEntity {
// ... standard AppEntity members ...
static var urlRepresentation: some URLRepresentation {
URLRepresentation {
"https://myapp.com/recipes/\(\.id)"
}
}
}enum RecipeCategory: String, URLRepresentableEnum {
case breakfast, lunch, dinner
static var urlRepresentation: some URLRepresentation {
URLRepresentation {
"https://myapp.com/category/\(\.self)"
}
}
// ... standard AppEnum members ...
}Conform to IndexedEntity to make entities searchable in Spotlight.
struct ArticleEntity: IndexedEntity {
static let defaultQuery = ArticleQuery()
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Article"
var id: String
@Property(title: "Title")
var title: String
@Property(title: "Author")
var author: String
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(title)", subtitle: "\(author)")
}
// Custom attribute set for improved search accuracy
var attributeSet: CSSearchableItemAttributeSet? {
let attrs = CSSearchableItemAttributeSet(contentType: .text)
attrs.title = title
attrs.authorNames = [author]
return attrs
}
}extension ArticleEntity {
var hideInSpotlight: Bool {
isDraft // Draft articles should not appear in search
}
}@ComputedProperty(indexingKey:) for Spotlight (iOS 26+)Use indexing keys on @Property and @ComputedProperty for structured Spotlight
metadata without manual CSSearchableItemAttributeSet configuration.
struct RecipeEntity: IndexedEntity {
static let defaultQuery = RecipeQuery()
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Recipe"
var id: String
@Property(title: "Name", indexingKey: .title)
var name: String
@Property(title: "Cuisine")
var cuisine: String
@ComputedProperty(indexingKey: .description)
var summary: String {
"\(name) -- \(cuisine) cuisine"
}
@ComputedProperty(indexingKey: .thumbnailURL)
var imageURL: URL? {
URL(string: "https://myapp.com/images/\(id).jpg")
}
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(name)", subtitle: "\(cuisine)")
}
}// WRONG: Manual attributeSet when indexing keys are available (iOS 26+)
struct RecipeEntity: IndexedEntity {
@Property(title: "Name")
var name: String
var attributeSet: CSSearchableItemAttributeSet? {
let attrs = CSSearchableItemAttributeSet(contentType: .text)
attrs.title = name // Redundant on iOS 26
return attrs
}
}
// CORRECT: Use indexingKey for structured metadata (iOS 26+)
struct RecipeEntity: IndexedEntity {
@Property(title: "Name", indexingKey: .title)
var name: String
}| Key | Property Type | Purpose |
|---|---|---|
.title | String | Primary searchable title |
.description | String | Detailed description |
.thumbnailURL | URL? | Thumbnail image |
.keywords | [String] | Additional search terms |
.contentURL | URL? | Content location |
Make onscreen content available to Siri and Apple Intelligence without an assistant schema:
struct ArticleEntity: AppEntity, Transferable {
// Standard AppEntity conformance...
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .article)
}
}
// In your view controller or SwiftUI view:
let activity = NSUserActivity(activityType: "com.myapp.article")
activity.appEntityIdentifier = AppEntityIdentifier(article)
// Set as current activityUse When, Switch, Case, and DefaultCase for conditional parameter
summaries that change based on parameter values:
struct ConfigureWidgetIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Configure Widget"
@Parameter(title: "Style")
var style: WidgetStyle
@Parameter(title: "Show Details", default: false)
var showDetails: Bool
@Parameter(title: "Refresh Interval", default: .hourly)
var interval: RefreshInterval
static var parameterSummary: some ParameterSummary {
When(\.$showDetails, .equalTo, true) {
Summary("Show \(\.$style) widget") {
\.$showDetails
\.$interval
}
} otherwise: {
Summary("Show \(\.$style) widget") {
\.$showDetails
}
}
}
}static var parameterSummary: some ParameterSummary {
Switch(\.$style) {
Case(.compact) {
Summary("Compact widget")
}
Case(.detailed) {
Summary("Detailed widget") {
\.$interval
}
}
DefaultCase {
Summary("Widget") {
\.$style
}
}
}
}Use Core Spotlight directly when you need full control over indexing without
adopting App Intents, or when targeting iOS versions before IndexedEntity
(pre-iOS 18). For apps already using App Intents, prefer IndexedEntity
(iOS 18+) or @ComputedProperty(indexingKey:) (iOS 26+) instead.
| Approach | When to Use |
|---|---|
IndexedEntity (iOS 18+) | App already uses App Intents; entities are also Siri/Shortcuts-visible |
@ComputedProperty(indexingKey:) (iOS 26+) | Cleaner metadata mapping without manual CSSearchableItemAttributeSet |
| Core Spotlight directly | No App Intents adoption; pre-iOS 18 targets; standalone indexing; fine-grained control over expiration, domain grouping, or batch operations |
A CSSearchableItem uniquely identifies searchable content. Attach a
CSSearchableItemAttributeSet to describe the item's metadata.
Docs: CSSearchableItem, CSSearchableItemAttributeSet
import CoreSpotlight
import UniformTypeIdentifiers
func makeSearchableItem(
id: String,
title: String,
description: String,
thumbnailData: Data? = nil
) -> CSSearchableItem {
let attributes = CSSearchableItemAttributeSet(contentType: .text)
attributes.title = title
attributes.contentDescription = description
attributes.thumbnailData = thumbnailData
// Optional: improve search ranking and categorization
attributes.keywords = ["recipe", "cooking"]
attributes.displayName = title
attributes.contentURL = URL(string: "myapp://recipes/\(id)")
let item = CSSearchableItem(
uniqueIdentifier: id,
domainIdentifier: "com.myapp.recipes",
attributeSet: attributes
)
// Items expire after 30 days by default; customize if needed
item.expirationDate = Date.now.addingTimeInterval(60 * 60 * 24 * 90)
return item
}Use CSSearchableIndex to add, update, and remove items. The default()
index works for most apps. Use a named index with a protection class for
sensitive content.
Docs: CSSearchableIndex
import CoreSpotlight
// Index a single item (add or update)
func indexItem(_ item: CSSearchableItem) async throws {
try await CSSearchableIndex.default().indexSearchableItems([item])
}
// Delete specific items by identifier
func deleteItems(identifiers: [String]) async throws {
try await CSSearchableIndex.default().deleteSearchableItems(
withIdentifiers: identifiers
)
}
// Delete all items in a domain (e.g., after user deletes a category)
func deleteItemsInDomain(_ domain: String) async throws {
try await CSSearchableIndex.default().deleteSearchableItems(
withDomainIdentifiers: [domain]
)
}
// Delete everything (e.g., on logout)
func deleteAllItems() async throws {
try await CSSearchableIndex.default().deleteAllSearchableItems()
}For large data sets, index in batches to minimize memory pressure and handle
errors gracefully. Use beginBatch() / endBatch(withClientState:) to
track progress and resume after crashes.
import CoreSpotlight
func batchIndexRecipes(_ recipes: [Recipe]) async throws {
let index = CSSearchableIndex(name: "recipes")
// Simple batched approach -- chunk into groups
let batchSize = 100
for batch in stride(from: 0, to: recipes.count, by: batchSize) {
let end = min(batch + batchSize, recipes.count)
let items = recipes[batch..<end].map { recipe in
makeSearchableItem(
id: recipe.id,
title: recipe.name,
description: recipe.summary,
thumbnailData: recipe.thumbnailData
)
}
try await index.indexSearchableItems(items)
}
}
// Client-state-based batching for crash recovery
func batchIndexWithState(_ recipes: [Recipe]) async throws {
let index = CSSearchableIndex(name: "recipes")
// Check where we left off
let lastState = try await index.fetchLastClientState()
let startOffset = lastState.flatMap { Int(String(data: $0, encoding: .utf8) ?? "") } ?? 0
let batchSize = 100
for batch in stride(from: startOffset, to: recipes.count, by: batchSize) {
let end = min(batch + batchSize, recipes.count)
let items = recipes[batch..<end].map { recipe in
makeSearchableItem(
id: recipe.id,
title: recipe.name,
description: recipe.summary
)
}
index.beginBatch()
try await index.indexSearchableItems(items)
let stateData = "\(end)".data(using: .utf8)!
try await index.endBatch(withClientState: stateData)
}
}Use a named index with a data protection class to encrypt indexed content:
let protectedIndex = CSSearchableIndex(
name: "secure-notes",
protectionClass: .complete // Only accessible when device is unlocked
)
try await protectedIndex.indexSearchableItems(sensitiveItems)When a user taps a Spotlight result, the system delivers an NSUserActivity
with activityType set to CSSearchableItemActionType. Extract the item
identifier from userInfo to navigate to the correct content.
import CoreSpotlight
import UIKit
// UIKit: In AppDelegate or SceneDelegate
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
if userActivity.activityType == CSSearchableItemActionType,
let identifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String {
navigateToItem(withIdentifier: identifier)
return true
}
return false
}
// SwiftUI: Use onContinueUserActivity
struct ContentView: View {
var body: some View {
NavigationStack {
RecipeListView()
}
.onContinueUserActivity(CSSearchableItemActionType) { activity in
if let id = activity.userInfo?[CSSearchableItemActivityIdentifier] as? String {
navigateToRecipe(id: id)
}
}
}
}When a user taps "Search in App" from Spotlight, handle the query string:
// activityType == CSQueryContinuationActionType
.onContinueUserActivity(CSQueryContinuationActionType) { activity in
if let query = activity.userInfo?[CSSearchQueryString] as? String {
searchViewModel.searchText = query
}
}skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references