-
Descubre las nuevas capacidades del framework App Intents
Mejora la implementación de App Intents con funcionalidades avanzadas para que sea más rápida, flexible y relevante. Descubre cómo ValueRepresentation y RelevantEntities hacen que tu contenido sea más fácil de encontrar y permiten que se comparta entre apps, cómo EntityCollection mejora el rendimiento y cómo SyncableEntity te permite escalar entre dispositivos. Explora tipos de parámetros más completos, como los valores de unión y las intenciones de larga duración que manejan la cancelación de forma fluida.
Capítulos
- 0:00 - Introducción
- 2:40 - Comparte entidades entre apps con ValueRepresentation
- 3:45 - Registra entidades relevantes con RelevantEntities
- 7:05 - Gestiona las entidades de forma eficiente con EntityCollection
- 8:55 - Usa entidades en varios dispositivos con SyncableEntity
- 11:01 - Tipos de parámetros más avanzados
- 12:38 - Parámetros de valores de unión
- 13:26 - Amplía la ejecución con LongRunningIntent
- 15:27 - Enfoca la ejecución en el proceso correcto con ExecutionTargets
- 17:14 - Próximos pasos
Recursos
-
Buscar este video…
-
-
0:01 - Share structured entities with ValueRepresentation
struct LandmarkEntity: AppEntity, Transferable { var id: Int var landmark: Landmark // contains CLLocationCoordinate2D static var transferRepresentation: some TransferRepresentation { ValueRepresentation( exporting: { entity in PlaceDescriptor( representations: [.coordinate(entity.landmark.locationCoordinate)], commonName: entity.landmark.name ) } ) } } // If the entity already has a PlaceDescriptor property, use a key-path — much less code: struct LandmarkEntity: AppEntity, Transferable { var id: Int @Property var placeDescriptor: PlaceDescriptor static var transferRepresentation: some TransferRepresentation { ValueRepresentation(exporting: \.placeDescriptor) } } -
5:18 - Register relevant entities with RelevantEntities
// Suggest playlists for the workout session let playlistEntities = [dailyRun, runningMix] let workoutContext = AppEntityContext.audio(.workout(activityType: .running)) try await RelevantEntities.shared.updateEntities( playlistEntities, for: workoutContext ) // Clear all entities for a context try await RelevantEntities.shared.removeAllEntities(for: workoutContext) // Remove specific entities from a context try await RelevantEntities.shared.removeEntities(playlistEntities, from: workoutContext) // Or remove all entities across all contexts try await RelevantEntities.shared.removeAllEntities() -
7:15 - Handle large entity sets with EntityCollection
struct TagPhotosIntent: AppIntent { static let title: LocalizedStringResource = "Tag Travel Photos" @Parameter var photos: EntityCollection<PhotoEntity> // was: [PhotoEntity] @Parameter var tag: String func perform() async throws -> some IntentResult { modelData.tagPhotos(ids: photos.identifiers, tag: tag) // was: tagPhotos(photos, tag: tag) return .result() } } -
10:14 - Make entity IDs stable with SyncableEntity
// If your ID is already stable across devices (server UUID, CloudKit record ID): struct PhotoEntity: AppEntity, SyncableEntity { var id: Int // Already stable across devices — that's it } // If you use local IDs, pair a local and a stable ID: struct PhotoEntity: AppEntity, SyncableEntity { var id: SyncableEntityIdentifier<String, String> init(localID: String, stableID: String) { self.id = SyncableEntityIdentifier(local: localID, stable: stableID) } } -
11:58 - Accept multiple types with @UnionValue
@UnionValue enum TravelGalleryContent { case landmarkCollection(LandmarkCollectionEntity) case photoAlbum(PhotoAlbumEntity) static let typeDisplayRepresentation: TypeDisplayRepresentation = "Travel Gallery" static let caseDisplayRepresentations: [Cases: DisplayRepresentation] = [ .landmarkCollection: "Landmark Collection", .photoAlbum: "Photo Album" ] } -
13:41 - Run beyond 30 s with LongRunningIntent + CancellableIntent
struct UploadPhotoIntent: LongRunningIntent, CancellableIntent { static let title: LocalizedStringResource = "Upload Photo" @Parameter var photo: IntentFile func perform() async throws -> some IntentResult & ProvidesDialog { let result = try await performBackgroundTask { let chunks = calculateChunks(for: photo) progress.totalUnitCount = Int64(chunks) for chunk in 1...chunks { try Task.checkCancellation() try await uploadChunk(chunk) progress.completedUnitCount = Int64(chunk) } return "Upload complete!" } onCancel: { reason in cleanup(for: reason) } return .result(dialog: "\(result)") } } -
16:54 - Control which process runs your intent with ExecutionTargets
// Write operation — needs the main app struct UpdateFavoriteIntent: AppIntent { static var allowedExecutionTargets: ExecutionTargets { .main } } // Standalone download — runs in the extension struct DownloadPhotoIntent: AppIntent { static var allowedExecutionTargets: ExecutionTargets { .appIntentsExtension } } // Display-only — runs in the widget extension struct GetLandmarkStatusIntent: AppIntent { static var allowedExecutionTargets: ExecutionTargets { .widgetKitExtension } } // Works in either — lets the system choose struct TagPhotosIntent: AppIntent { static var allowedExecutionTargets: ExecutionTargets { [.main, .appIntentsExtension] } }
-