-
Explora nuevos avances en App Intents
Explora todas las nuevas mejoras disponibles en la estructura App Intents en las versiones de este año. Obtén información sobre las mejoras en la calidad de vida de los desarrolladores, como propiedades diferidas, nuevas capacidades como fragmentos de App Intents interactivos, anotaciones de vista de entidad, cómo integrar Visual Intelligence y mucho más. Te mostraremos cómo App Intents es más expresivo que nunca y, al mismo tiempo, se vuelve más fácil y sencillo de adoptar. También compartiremos nuevos y emocionantes clientes de App Intents este año, como Spotlight y Visual Intelligence, y aprenderemos a escribir App Intents que funcionen muy bien en esos contextos.
Capítulos
- 0:00 - Introducción
- 0:55 - Fragmentos interactivos
- 8:15 - Nuevas integraciones de sistemas
- 15:01 - Mejoras en la experiencia de usuario
- 21:02 - API de conveniencia
Recursos
- Adopting App Intents to support system experiences
- Building a workout app for iPhone and iPad
- Accelerating app interactions with App Intents
- App intent domains
- Creating your first app intent
- Integrating actions with Siri and Apple Intelligence
- Making actions and content discoverable and widely available
- PurchaseIntent
- App Shortcuts
- App Intents
Videos relacionados
WWDC25
- Conoce App Intents
- Desarrolla para Atajos y Spotlight con App Intents
- Diseña fragmentos interactivos
WWDC24
-
Buscar este video…
-
-
4:08 - Returning a Snippet Intent
import AppIntents import SwiftUI struct ClosestLandmarkIntent: AppIntent { static let title: LocalizedStringResource = "Find Closest Landmark" @Dependency var modelData: ModelData func perform() async throws -> some ReturnsValue<LandmarkEntity> & ShowsSnippetIntent & ProvidesDialog { let landmark = await self.findClosestLandmark() return .result( value: landmark, dialog: IntentDialog( full: "The closest landmark is \(landmark.name).", supporting: "\(landmark.name) is located in \(landmark.continent)." ), snippetIntent: LandmarkSnippetIntent(landmark: landmark) ) } } -
4:31 - Building a SnippetIntent
struct LandmarkSnippetIntent: SnippetIntent { static let title: LocalizedStringResource = "Landmark Snippet" @Parameter var landmark: LandmarkEntity @Dependency var modelData: ModelData func perform() async throws -> some IntentResult & ShowsSnippetView { let isFavorite = await modelData.isFavorite(landmark) return .result( view: LandmarkView(landmark: landmark, isFavorite: isFavorite) ) } } -
5:45 - Associate intents with buttons
struct LandmarkView: View { let landmark: LandmarkEntity let isFavorite: Bool var body: some View { // ... Button(intent: UpdateFavoritesIntent(landmark: landmark, isFavorite: !isFavorite)) { /* ... */ } Button(intent: FindTicketsIntent(landmark: landmark)) { /* ... */ } // ... } } -
6:53 - Request confirmation snippet
struct FindTicketsIntent: AppIntent { func perform() async throws -> some IntentResult & ShowsSnippetIntent { let searchRequest = await searchEngine.createRequest(landmarkEntity: landmark) // Present a snippet that allows people to change // the number of tickets. try await requestConfirmation( actionName: .search, snippetIntent: TicketRequestSnippetIntent(searchRequest: searchRequest) ) // Resume searching... } } -
7:24 - Using Entities as parameters
struct TicketRequestSnippetIntent: SnippetIntent { static let title: LocalizedStringResource = "Ticket Request Snippet" @Parameter var searchRequest: SearchRequestEntity func perform() async throws -> some IntentResult & ShowsSnippetView { let view = TicketRequestView(searchRequest: searchRequest) return .result(view: view) } } -
8:01 - Updating a snippet
func performRequest(request: SearchRequestEntity) async throws { // Set to pending status... TicketResultSnippetIntent.reload() // Kick off search... TicketResultSnippetIntent.reload() } -
9:24 - Responding to Image Search
struct LandmarkIntentValueQuery: IntentValueQuery { @Dependency var modelData: ModelData func values(for input: SemanticContentDescriptor) async throws -> [LandmarkEntity] { guard let pixelBuffer: CVReadOnlyPixelBuffer = input.pixelBuffer else { return [] } let landmarks = try await modelData.searchLandmarks(matching: pixelBuffer) return landmarks } } -
9:51 - Support opening an entity
struct OpenLandmarkIntent: OpenIntent { static var title: LocalizedStringResource = "Open Landmark" @Parameter(title: "Landmark") var target: LandmarkEntity func perform() async throws -> some IntentResult { /// ... } } -
10:53 - Show search results in app
@AppIntent(schema: .visualIntelligence.semanticContentSearch) struct ShowSearchResultsIntent { var semanticContent: SemanticContentDescriptor @Dependency var navigator: Navigator func perform() async throws -> some IntentResult { await navigator.showImageSearch(semanticContent.pixelBuffer) return .result() } // ... } -
11:40 - Returning multiple entity types
@UnionValue enum VisualSearchResult { case landmark(LandmarkEntity) case collection(CollectionEntity) }a struct LandmarkIntentValueQuery: IntentValueQuery { func values(for input: SemanticContentDescriptor) async throws -> [VisualSearchResult] { // ... } } struct OpenLandmarkIntent: OpenIntent { /* ... */ } struct OpenCollectionIntent: OpenIntent { /* ... */ } -
13:00 - Associating a view with an AppEntity
struct LandmarkDetailView: View { let landmark: LandmarkEntity var body: some View { Group{ /* ... */ } .userActivity("com.landmarks.ViewingLandmark") { activity in activity.title = "Viewing \(landmark.name)" activity.appEntityIdentifier = EntityIdentifier(for: landmark) } } } -
13:21 - Converting AppEntity to PDF
import CoreTransferable import PDFKit extension LandmarkEntity: Transferable { static var transferRepresentation: some TransferRepresentation { DataRepresentation(exportedContentType: .pdf) {landmark in // Create PDF data... return data } } } -
14:05 - Associating properties with Spotlight keys
struct LandmarkEntity: IndexedEntity { // ... @Property(indexingKey: \.displayName) var name: String @Property(customIndexingKey: /* ... */) var continent: String // ... } -
15:49 - Making intents undoable
struct DeleteCollectionIntent: UndoableIntent { // ... func perform() async throws -> some IntentResult { // Confirm deletion... await undoManager?.registerUndo(withTarget: modelData) {modelData in // Restore collection... } await undoManager?.setActionName("Delete \(collection.name)") // Delete collection... } } -
16:52 - Multiple choice
struct DeleteCollectionIntent: UndoableIntent { func perform() async throws -> some IntentResult & ReturnsValue<CollectionEntity?> { let archive = Option(title: "Archive", style: .default) let delete = Option(title: "Delete", style: .destructive) let resultChoice = try await requestChoice( between: [.cancel, archive, delete], dialog: "Do you want to archive or delete \(collection.name)?", view: collectionSnippetView(collection) ) switch resultChoice { case archive: // Archive collection... case delete: // Delete collection... default: // Do nothing... } } // ... } -
18:47 - Supported modes
struct GetCrowdStatusIntent: AppIntent { static let supportedModes: IntentModes = [.background, .foreground] func perform() async throws -> some ReturnsValue<Int> & ProvidesDialog { if systemContext.currentMode == .foreground { await navigator.navigateToCrowdStatus(landmark) } // Retrieve status and return dialog... } } -
19:30 - Supported modes
struct GetCrowdStatusIntent: AppIntent { static let supportedModes: IntentModes = [.background, .foreground(.dynamic)] func perform() async throws -> some ReturnsValue<Int> & ProvidesDialog { guard await modelData.isOpen(landmark) else { /* Exit early... */ } if systemContext.currentMode.canContinueInForeground { do { try await continueInForeground(alwaysConfirm: false) await navigator.navigateToCrowdStatus(landmark) } catch { // Open app denied. } } // Retrieve status and return dialog... } } -
21:30 - View Control
extension OpenLandmarkIntent: TargetContentProvidingIntent {} struct LandmarksNavigationStack: View { @State var path: [Landmark] = [] var body: some View { NavigationStack(path: $path) { /* ... */ } .onAppIntentExecution(OpenLandmarkIntent.self) { intent in self.path.append(intent.landmark) } } } -
23:13 - Scene activation condition
@main struct AppIntentsTravelTrackerApp: App { var body: some Scene { WindowGroup { /* ... */ } WindowGroup { /* ... */ } .handlesExternalEvents(matching: [ OpenLandmarkIntent.persistentIdentifier ]) } } -
23:33 - View activation condition
struct LandmarksNavigationStack: View { var body: some View { NavigationStack(path: $path) { /* ... */ } .handlesExternalEvents( preferring: [], allowing: !isEditing ? [OpenLandmarkIntent.persistentIdentifier] : [] ) } } -
24:23 - Computed property
struct SettingsEntity: UniqueAppEntity { @ComputedProperty var defaultPlace: PlaceDescriptor { UserDefaults.standard.defaultPlace } init() { } } -
24:48 - Deferred property
struct LandmarkEntity: IndexedEntity { // ... @DeferredProperty var crowdStatus: Int { get async throws { await modelData.getCrowdStatus(self) } } // ... } -
25:50 - AppIntentsPackage
// Framework or dynamic library public struct LandmarksKitPackage: AppIntentsPackage { } // App target struct LandmarksPackage: AppIntentsPackage { static var includedPackages: [any AppIntentsPackage.Type] { [LandmarksKitPackage.self] } }
-
-
- 0:00 - Introducción
App Intents es una estructura que permite que las apps integren funcionalidades en todos los dispositivos, como Inteligencia Visual, Atajos y Spotlight. Obtén información sobre nuevos fragmentos interactivos, integración de apps en todo el sistema, experiencias de usuario refinadas y API convenientes para desarrolladores.
- 0:55 - Fragmentos interactivos
Con las actualizaciones más recientes, ahora puedes mejorar tus apps con fragmentos interactivos. Estos fragmentos son vistas dinámicas en las que se muestra información personalizada basada en App Intents. Por ejemplo, una app de jardinería puede sugerir encender los aspersores, o una app de pedidos de comida puede permitir a las personas configurar sus pedidos antes de realizarlos. La app de muestra TravelTracking es un excelente ejemplo. Cuando un usuario busca el punto de referencia más cercano, aparece un fragmento interactivo. Este fragmento incluye el nombre del punto de referencia y un botón con forma de corazón. Al tocar el botón del corazón, la persona puede marcar como favorito el punto de referencia y el fragmento se actualiza al instante para reflejar el nuevo estado, todo sin salir de la vista actual. Esta interactividad se consigue mediante el nuevo protocolo “SnippetIntent”. Puedes crear fragmentos de resultados en los que se muestre información después de una acción, y estos fragmentos pueden incluir botones o conmutadores que activen otras Intents de la app. Por ejemplo, en la app TravelTracking, el botón de corazón ejecuta la Intent Actualizar favoritos y puedes agregar un botón Buscar boletos para iniciar otra Intent. Cuando alguien interactúa con estos botones, el sistema ejecuta la Intent correspondiente y el fragmento se actualiza en consecuencia. El sistema garantiza que los datos estén siempre actualizados al obtener entidades de la app a partir de consultas cada vez que se actualiza el fragmento. Este proceso es fluido y animado, lo que proporciona una experiencia de usuario óptima. También puedes crear fragmentos de confirmación que soliciten a las personas que proporcionen información adicional antes de continuar. Por ejemplo, en la app TravelTracking, cuando alguien toca Buscar boletos, aparece un fragmento de confirmación que pregunta cuántos boletos desea buscar. Luego, la persona puede interactuar con este fragmento y el sistema actualiza la vista en tiempo real en función de sus entradas.
- 8:15 - Nuevas integraciones de sistemas
App Intents en iOS 26 mejora la integración del sistema, lo que permite la búsqueda de imágenes directamente desde capturas de cámara o capturas de pantalla. Las apps ahora pueden mostrar los resultados de búsqueda en el panel de búsqueda del sistema. Para respaldar esta capacidad, implementa consultas que se ajusten al protocolo “IntentValueQuery” y procesen datos “SemanticContentDescriptor” para devolver conjuntos de entidades de la app. Cuando alguien toca un resultado, se activa la “OpenIntent” correspondiente, que abre la app en la página correspondiente. Las “OpenIntents” no se limitan a la búsqueda de imágenes y también puedes usarlas en Spotlight. Considera optimizar el rendimiento de la búsqueda, devolver varias páginas de resultados y permitir a los usuarios continuar la búsqueda dentro de la app. Más allá de la búsqueda de imágenes, las App Intents habilitan entidades en pantalla, lo que permite a las personas interactuar con Siri y ChatGPT sobre el contenido visible en la app. Puedes asociar entidades con vistas, adaptarlas al protocolo “Transferable” y admitir varios tipos de datos, como PDF, texto simple y texto enriquecido. Para mejorar la búsqueda en Spotlight, haz que las entidades de la app se ajusten a “IndexedEntity”, dónalas a Spotlight y anota el contenido en pantalla con entidades. La implementación de “PredictableIntent” permite que el sistema aprenda del comportamiento del usuario y proporcione sugerencias personalizadas.
- 15:01 - Mejoras en la experiencia de usuario
Las nuevas funcionalidades de la plataforma de desarrollo de apps mejoran las experiencias de los usuarios a través de una funcionalidad mejorada de deshacer, opciones de opción múltiple y modos compatibles. El protocolo “UndoableIntent” permite a las personas revertir acciones realizadas con App Intents usando gestos familiares, lo que proporciona una red de seguridad para la experimentación. Puedes implementar esto adaptando las Intents al protocolo y registrando acciones de deshacer con el administrador de deshacer. Con la API de opción múltiple, puedes presentar a las personas varias opciones para una Intent, en lugar de solo una confirmación binaria. También puedes personalizarla con un diálogo y una vista SwiftUI personalizada. Los modos compatibles brindan a las Intents un mayor control sobre la puesta en primer plano de la app. Esta capacidad permite que las Intents se comporten de forma diferente en función de cómo interactúe una persona con el dispositivo. Por ejemplo, una Intent puede proporcionar información solo de voz cuando la persona está conduciendo, pero llevarla directamente a la app cuando está mirando el dispositivo. Puedes especificar los modos admitidos para sus Intents y usar la propiedad “currentMode” para verificar qué modo está activo.
- 21:02 - API de conveniencia
Con las nuevas API de control de vista en SwiftUI, puedes refactorizar las Intents de la app eliminando el código de IU y permitiendo que las vistas manejen la navegación directamente. Usa el modificador de vista “onAppIntentExecution”, que permite que las vistas respondan a app intents específicas y modifiquen la IU en consecuencia. El sistema ejecuta el cierre de la acción poco antes de poner en primer plano la app, y varias vistas pueden responder a la misma Intent. Puedes controlar qué escena maneja una Intent mediante las API “handlesExternalEvents”, lo que garantiza una navegación adecuada al contexto. Además, nuevas macros como “ComputedProperty” y “DeferredProperty” optimizan “AppEntities”, lo que reduce los costos de almacenamiento e instanciación. Las App Intents ahora también se pueden empaquetar en paquetes de Swift, lo que proporciona mayor flexibilidad y reutilización.