Tener un buscador dentro de una app está bien. Pero poder llegar a él diciendo «Busca taladros en Taller» ahorra unos cuantos pasos. Con App Intents podemos conectar esa petición con nuestra pantalla de resultados y conservar la experiencia de búsqueda que ya hemos construido.
La integración requiere algo más que abrir la aplicación: hay que transmitir la consulta, mostrar la pantalla adecuada y hacer que ambos caminos, la voz y el teclado, utilicen el mismo estado.
Un esquema que describe la acción
En iOS 27, el esquema system.searchInApp permite describir una acción que lleva al usuario a los resultados de búsqueda. Pertenece al dominio system, por lo que puede encajar en aplicaciones de categorías muy diferentes.
La macro @AppIntent(schema:) comprueba que la implementación cumple el contrato del esquema y añade las conformidades necesarias. Así el sistema recibe una descripción estructurada de la acción y sus parámetros.
Apple presenta searchInApp como la evolución del anterior system.search. Conviene distinguir esta novedad del protocolo ShowInAppSearchResultsIntent, que ya estaba disponible desde iOS 17.2. En este artículo veremos ejemplos utilizando el nuevo esquema presente desde iOS 27.
Compartir el estado con SwiftUI
Imaginemos una app llamada Taller que guarda un inventario de herramientas. Para simplificar el ejemplo, utilizaremos un objeto observable compartido entre el intent y la interfaz:
import Observation
@MainActor
@Observable
final class ToolNavigation {
static let shared = ToolNavigation()
var query = ""
var isSearchPresented = false
func showSearch(for term: String) {
query = term
isSearchPresented = true
}
}
@MainActor concentra las modificaciones del estado de navegación en el actor principal. La instancia compartida mantiene el ejemplo pequeño; en una app con varias ventanas convendría resolver qué escena debe recibir la petición, en lugar de compartir indiscriminadamente su navegación.
Recibir la consulta desde Siri
El intent recibe un StringSearchCriteria. Su propiedad term contiene el texto que debemos trasladar al buscador:
import AppIntents
@available(iOS 27.0, *)
@AppIntent(schema: .system.searchInApp)
struct SearchToolsIntent: ShowInAppSearchResultsIntent {
static let searchScopes: [StringSearchScope] = [.general]
var criteria: StringSearchCriteria
@MainActor
func perform() async throws -> some IntentResult {
ToolNavigation.shared.showSearch(for: criteria.term)
return .result()
}
}
El ámbito .general corresponde a una búsqueda textual general. perform() actualiza la consulta y solicita mostrar los resultados; la interfaz se ocupa de representarlos.
Este intent debe ejecutarse en la aplicación. ShowInAppSearchResultsIntent proporciona un modo de ejecución en primer plano, por lo que no hace falta añadir openAppWhenRun para reproducir ese comportamiento. Si el código vive en un módulo compartido con extensiones, hay que revisar los destinos de ejecución permitidos.
Mostrar los resultados
La vista raíz observa la misma instancia. Cuando cambia isSearchPresented, presenta el buscador, y el campo de texto queda conectado a la consulta recibida:
import SwiftUI
struct WorkshopView: View {
@Bindable private var navigation = ToolNavigation.shared
private let tools = [
"Taladro percutor", "Taladro de columna", "Lijadora"
]
private var results: [String] {
tools.filter {
navigation.query.isEmpty ||
$0.localizedStandardContains(navigation.query)
}
}
var body: some View {
NavigationStack {
Button("Buscar herramientas") {
navigation.showSearch(for: "")
}
.navigationTitle("Taller")
.navigationDestination(
isPresented: $navigation.isSearchPresented
) {
List(results, id: \.self) { tool in
Text(tool)
}
.navigationTitle("Herramientas")
.searchable(text: $navigation.query)
}
}
}
}
Aquí filtramos un pequeño array en memoria. En producción, sustituiríamos ese cálculo por el servicio de búsqueda existente. Una petición que llega desde Siri debería recorrer las mismas reglas de filtrado que una consulta escrita manualmente.
Abrir resultados y encontrar entidades
Esta integración puede adoptarse sin indexar previamente todo el contenido en Spotlight. Su función es llevar una consulta a la experiencia de búsqueda de la app.
Apple también ofrece otros mecanismos: IndexedEntity permite aportar entidades al índice del sistema, e IntentValueQuery permite resolver búsquedas estructuradas. Son capacidades complementarias; abrir una lista filtrada no implementa por sí solo esas integraciones.
Comprobar el recorrido completo
Conviene verificar el intent desde Atajos y después probar las peticiones con Siri en un dispositivo compatible. Además del resultado correcto, hay que probarlo con la aplicación cerrada, con una búsqueda ya abierta y con una consulta sin coincidencias.
Si existen pantallas de bienvenida o inicio de sesión, una decisión práctica es conservar la consulta hasta que la app pueda mostrarla. En búsquedas remotas también merece la pena cancelar peticiones anteriores para impedir que una respuesta antigua sobreescriba los resultados nuevos.
El valor de esta integración está en reutilizar el buscador y la navegación de la aplicación. Siri aporta otra puerta de entrada; mantener una única lógica de búsqueda hace que la experiencia siga siendo coherente desde el primer resultado.