-
Explore os novos avanços do framework App Intents
Explore todos os aprimoramentos disponíveis nas versões deste ano do framework App Intents. Saiba mais sobre melhorias que facilitam o dia a dia dos desenvolvedores, como propriedades adiadas (deferred properties), novos recursos como trechos interativos de App Intents, anotações de visualização de entidades, integração com Inteligência Visual e muito mais. Mostraremos como o App Intents está mais expressivo e ainda mais simples e fácil de adotar. Você também vai conhecer os novos clientes do App Intents, como o Spotlight e a Inteligência Visual, e aprender a criar app intents que funcionam perfeitamente nesses contextos.
Capítulos
- 0:00 - Introdução
- 0:55 - Snippets interativos
- 8:15 - Novas integrações do sistemas
- 15:01 - Melhorias na experiência do usuário
- 21:02 - APIs de conveniência
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
Vídeos relacionados
WWDC25
- Conheça o App Intents
- Crie snippets interativos
- Desenvolva para o app Atalhos e o Spotlight com o App Intents
WWDC24
-
Buscar neste vídeo...
-
-
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 - Introdução
O App Intents é um framework que permite aos apps integrar recursos entre dispositivos, como Atalhos, Spotlight e Inteligência Visual. Saiba mais sobre os novos snippets interativos, a integração com apps em todo o sistema, as experiências do usuário aprimoradas e as APIs práticas para desenvolvedores.
- 0:55 - Snippets interativos
Com as atualizações mais recentes, agora você pode aprimorar seus apps com snippets interativos. Eles são visualizações dinâmicas que exibem informações personalizadas com base no App Intents. Por exemplo, um app de jardinagem pode sugerir ligar os sprinklers, e um app de pedidos de comida pode permitir que a pessoa personalize o pedido antes de finalizá-lo. O app de exemplo TravelTracking ilustra bem esse caso. Quando um usuário pesquisa o ponto turístico mais próximo, um fragmento interativo aparece. Ele inclui o nome do ponto turístico e um botão de coração. Ao tocar no botão de coração, a pessoa pode adicionar o ponto turístico aos favoritos e o fragmento é atualizado instantaneamente para refletir esse novo status. Tudo isso sem sair da visualização atual. Essa interatividade é possível graças ao novo protocolo SnippetIntent. Com ele, é possível criar snippets de resultado que exibem informações após uma ação, e esses snippets podem incluir botões ou controles que acionam outros App Intents. Por exemplo, no app TravelTracking, o botão de coração executa a Intent Atualizar Favoritos, e você pode adicionar um botão Encontrar Passagens para acionar outra Intent. Quando alguém interage com esses botões, o sistema executa a Intent correspondente e o fragmento é atualizado, conforme necessário. O sistema garante que os dados estejam sempre atualizados ao buscar entidades do app por meio de consultas sempre que o fragmento é atualizado. Esse processo é contínuo e animado, oferecendo uma experiência de uso fluida. Você também pode criar snippets de confirmação que solicitam informações adicionais antes de continuar. Por exemplo, no app TravelTracking, quando alguém toca em Encontrar Passagens, aparece um fragmento de confirmação pedindo quantos ingressos a pessoa deseja procurar. Em seguida, a pessoa pode interagir com esse fragmento, e o sistema atualiza a visualização em tempo real com base nas entradas.
- 8:15 - Novas integrações do sistemas
O App Intents no iOS 26 aprimora a integração do sistema, permitindo a pesquisa de imagens diretamente usando gravações da câmera ou capturas de tela. Agora, os apps podem exibir os resultados da busca no painel de pesquisa do sistema. Para possibilitar essa funcionalidade, implemente consultas que estejam em conformidade com o protocolo IntentValueQuery, processando dados do SemanticContentDescriptor para retornar matrizes de entidades do app. Quando alguém toca em um resultado, a OpenIntent correspondente é acionada, abrindo o app na página relevante. As OpenIntents não se limitam à busca de imagens. Você também pode usá-las no Spotlight. Considere otimizar o desempenho da pesquisa, retornar várias páginas de resultados e permitir que a pessoa continue a busca dentro do app. Além da busca de imagens, o App Intents também é compatível com entidades visíveis na tela, permitindo que as pessoas interajam com a Siri e o ChatGPT sobre o conteúdo mostrado no app. É possível associar entidades a visualizações, fazê-las adotar o protocolo Transferable e aceitar diversos tipos de dados, como PDF, texto simples e texto com formatação. Para aprimorar a busca no Spotlight, faça com que as entidades do app adotem o protocolo IndexedEntity, doe essas entidades ao Spotlight e anote o conteúdo exibido na tela com elas. A implementação do protocolo PredictableIntent permite que o sistema aprenda com o comportamento do usuário e forneça sugestões personalizadas.
- 15:01 - Melhorias na experiência do usuário
Os novos recursos da plataforma de desenvolvimento de apps aprimoram a experiência do usuário com melhorias na funcionalidade de desfazer, opções de múltipla escolha e modos compatíveis. O protocolo UndoableIntent permite que as pessoas revertam ações realizadas com o App Intents usando gestos já conhecidos, oferecendo uma rede de segurança para experimentações. Para implementar, basta fazer com que as Intents adotem o protocolo, além de registrar ações de desfazer com o Undo Manager. Com a API multiple-choice, é possível apresentar várias opções para uma Intent em vez de apenas uma confirmação binária. Você também pode personalizar a experiência com uma caixa de diálogo e uma view personalizada do SwiftUI. Os modos compatíveis oferecem às Intents mais controle sobre a abertura do app em primeiro plano. Essa funcionalidade permite que as Intents se comportem de maneiras diferentes, dependendo de como a pessoa interage com o dispositivo. Por exemplo, uma Intent pode fornecer informações apenas por voz quando a pessoa estiver dirigindo, mas levá-la diretamente ao app quando ela estiver olhando para a tela. Você pode definir os modos compatíveis para as Intents e usar a propriedade currentMode para verificar qual modo está ativo.
- 21:02 - APIs de conveniência
Com as novas APIs de controle de view no SwiftUI, você pode refatorar seus App Intents removendo o código de interface e permitindo que as visualizações lidem diretamente com a navegação. Use o modificador de view onAppIntentExecution, que permite que as visualizações respondam a App Intents específicos e modifiquem a interface conforme necessário. O sistema executa o bloco de ação pouco antes de trazer o app para o primeiro plano, e várias visualizações podem responder à mesma Intent. É possível escolher qual cena deve lidar com uma Intent usando as APIs handlesExternalEvents, o que garante uma navegação adequada ao contexto. Além disso, novas macros como ComputedProperty e DeferredProperty otimizam AppEntities, reduzindo os custos de armazenamento e instanciação. Agora, os App Intents também podem ser agrupados em pacotes do Swift, oferecendo mais flexibilidade e reusabilidade.