Programación en Swift y SwiftUI para iOS Developers

searchFocused en SwiftUI

Como iOS Developer, sabes que la experiencia de usuario (UX) lo es todo. Una de las interacciones más comunes en cualquier aplicación moderna es la búsqueda. Los usuarios esperan encontrar lo que necesitan rápidamente, y cualquier fricción en este proceso puede resultar en frustración. Aquí es donde brilla la programación Swift, y más específicamente, el framework declarativo de Apple.

En este artículo, exploraremos qué es el modificador searchFocused en SwiftUI, cómo interactúa con el estado de la vista y cómo puedes implementarlo usando Xcode para crear experiencias de búsqueda fluidas e intuitivas a través de todo el ecosistema de Apple: iOS, macOS y watchOS.


¿Qué es searchFocused en SwiftUI?

Introducido en las actualizaciones recientes de SwiftUI, searchFocused es un modificador de vista (un “método de instancia” en la jerga de la documentación de Apple) que te permite controlar programáticamente el estado de enfoque (focus) de un campo de búsqueda generado por el modificador .searchable.

Antes de la llegada de las APIs de enfoque en SwiftUI, controlar cuándo el teclado aparecía o desaparecía en una barra de búsqueda requería “hacks” usando UIViewRepresentable para acceder a la API de UIKit subyacente (UISearchBar). Hoy en día, usando Swift, podemos lograr esto de manera puramente declarativa.

La anatomía del enfoque

Para usar searchFocused, necesitas entender su compañero inseparable: @FocusState.

Un @FocusState es un envoltorio de propiedad (property wrapper) que SwiftUI utiliza para rastrear qué elemento de la interfaz de usuario tiene actualmente el foco del sistema (por ejemplo, quién está recibiendo la entrada del teclado). Al vincular un @FocusState con el modificador searchFocused, puedes:

  1. Leer si la barra de búsqueda tiene el foco actualmente.
  2. Forzar a la barra de búsqueda a que tome el foco (desplegando el teclado automáticamente).
  3. Quitar el foco de la barra de búsqueda (ocultando el teclado).

Requisitos Previos y Entorno de Desarrollo

Para aprovechar este tutorial y escribir el código en Xcode, asegúrate de cumplir con los siguientes requisitos:

  • Entorno: Xcode 13 o superior (se recomienda Xcode 15+ para las últimas optimizaciones).
  • Lenguaje: Swift 5.5 o superior.
  • Sistemas Operativos:
    • iOS 15.0+
    • macOS 12.0+
    • watchOS 8.0+

Implementación Paso a Paso en iOS

Comencemos con la plataforma más común para un iOS Developer. Vamos a crear una lista de elementos y añadirle una barra de búsqueda. Queremos que, al pulsar un botón específico, el cursor salte automáticamente a la barra de búsqueda.

1. Preparando la Vista Base

Primero, configuramos nuestra vista con una lista de datos y el modificador .searchable.

import SwiftUI

struct iOSSearchView: View {
    @State private var searchText = ""
    let items = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]
    
    var filteredItems: [String] {
        if searchText.isEmpty {
            return items
        } else {
            return items.filter { $0.localizedCaseInsensitiveContains(searchText) }
        }
    }
    
    var body: some View {
        NavigationStack {
            List(filteredItems, id: \.self) { item in
                Text(item)
            }
            .navigationTitle("Frutas")
            // Aquí agregamos la capacidad de búsqueda
            .searchable(text: $searchText, prompt: "Buscar frutas...")
        }
    }
}

2. Integrando @FocusState y searchFocused

Ahora, vamos a añadir el control de enfoque. Declararemos una variable @FocusState y la vincularemos usando searchFocused en SwiftUI.

import SwiftUI

struct iOSSearchView: View {
    @State private var searchText = ""
    
    // 1. Declaramos el estado de enfoque
    @FocusState private var isSearchFieldFocused: Bool
    
    let items = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]
    
    var filteredItems: [String] {
        if searchText.isEmpty { return items }
        return items.filter { $0.localizedCaseInsensitiveContains(searchText) }
    }
    
    var body: some View {
        NavigationStack {
            VStack {
                // Botón para forzar el enfoque
                Button("¡Empezar a buscar!") {
                    // 3. Modificamos el estado para activar el teclado
                    isSearchFieldFocused = true
                }
                .padding()
                .buttonStyle(.borderedProminent)
                
                List(filteredItems, id: \.self) { item in
                    Text(item)
                }
            }
            .navigationTitle("Frutas")
            .searchable(text: $searchText, prompt: "Buscar frutas...")
            // 2. Vinculamos el estado de enfoque al campo de búsqueda
            .searchFocused($isSearchFieldFocused)
        }
    }
}

Análisis del código:

  • Al declarar @FocusState private var isSearchFieldFocused: Bool, le decimos a SwiftUI que rastree un valor booleano.
  • El modificador .searchFocused($isSearchFieldFocused) establece un enlace bidireccional. Si el usuario toca la barra de búsqueda, la variable se vuelve true. Si presionamos nuestro botón, cambiamos la variable a true mediante código, y SwiftUI reacciona dándole el foco a la barra y mostrando el teclado.

Adaptando el Código para macOS

La programación Swift moderna nos permite reutilizar gran parte de nuestro código, pero cada plataforma tiene sus matices. En macOS, no tenemos un teclado en pantalla que se despliegue, pero el concepto de “foco” sigue siendo crucial para que el usuario pueda empezar a escribir inmediatamente sin tener que hacer clic con el ratón.

En Xcode, puedes reutilizar casi exactamente el mismo código para macOS, pero la ubicación del campo de búsqueda a menudo reside en la barra de herramientas (Toolbar).

import SwiftUI

struct MacOSSearchView: View {
    @State private var searchText = ""
    @FocusState private var isSearchFocused: Bool
    
    let items = ["Xcode", "Swift", "SwiftUI", "Instruments", "Core Data"]
    
    var filteredItems: [String] {
        if searchText.isEmpty { return items }
        return items.filter { $0.localizedCaseInsensitiveContains(searchText) }
    }
    
    var body: some View {
        NavigationStack {
            List(filteredItems, id: \.self) { item in
                Text(item)
            }
            .navigationTitle("Herramientas Mac")
            .searchable(text: $searchText, placement: .toolbar, prompt: "Buscar herramienta...")
            .searchFocused($isSearchFocused)
            .toolbar {
                ToolbarItem(placement: .primaryAction) {
                    Button(action: {
                        // Da el foco al campo de búsqueda en la barra de herramientas
                        isSearchFocused = true
                    }) {
                        Image(systemName: "magnifyingglass")
                    }
                    .help("Ir a la búsqueda") // Tooltip nativo de macOS
                }
            }
        }
    }
}

Nota para el iOS Developer: Nota cómo en macOS usamos placement: .toolbar dentro de .searchable para que encaje con las directrices de diseño (HIG) de escritorio de Apple.


Llevándolo a la muñeca: watchOS

Implementar searchFocused en SwiftUI para watchOS presenta un desafío único debido al tamaño de la pantalla. En watchOS, la búsqueda a menudo despliega una pantalla de dictado o un teclado de tipo “Scribble/QWERTY” a pantalla completa.

Afortunadamente, el modificador se comporta de manera inteligente y nativa.

import SwiftUI

struct WatchOSSearchView: View {
    @State private var searchText = ""
    @FocusState private var isSearchFocused: Bool
    
    let contacts = ["Tim", "Craig", "Joz", "Phil"]
    
    var filtered: [String] {
        searchText.isEmpty ? contacts : contacts.filter { $0.contains(searchText) }
    }
    
    var body: some View {
        NavigationStack {
            List {
                Button("Buscar Contacto") {
                    // Activa inmediatamente la interfaz de entrada de texto de watchOS
                    isSearchFocused = true
                }
                .foregroundColor(.blue)
                
                ForEach(filtered, id: \.self) { contact in
                    Text(contact)
                }
            }
            .navigationTitle("Contactos")
            .searchable(text: $searchText)
            .searchFocused($isSearchFocused)
        }
    }
}

Casos de Uso Avanzados en SwiftUI

Múltiples Campos de Enfoque con Enums

A veces no solo rastreamos un valor booleano, sino que tenemos múltiples campos de texto en una vista (por ejemplo, un formulario y una barra de búsqueda). Para esto, un buen iOS Developer utiliza enum.

enum FocusField: Hashable {
    case searchBar
    case usernameField
    case passwordField
}

struct ComplexFormView: View {
    @State private var searchText = ""
    @State private var username = ""
    @FocusState private var focusedField: FocusField?
    
    var body: some View {
        NavigationStack {
            Form {
                TextField("Usuario", text: $username)
                    .focused($focusedField, equals: .usernameField)
                
                Button("Ir a buscar") {
                    focusedField = .searchBar
                }
            }
            .searchable(text: $searchText)
            // Usamos la variante del método que acepta un valor de comparación
            .searchFocused($focusedField, equals: .searchBar)
        }
    }
}

Esta es la belleza de la programación Swift: la seguridad de tipos (Type Safety). Al usar un Enum, eliminamos el riesgo de tener múltiples booleanos en estados contradictorios.

Auto-Enfoque al Cargar la Vista (Auto-focus on Appear)

Un patrón común en UX es abrir una vista de búsqueda y que el teclado ya esté visible.

Para lograr esto, podríamos pensar en usar .onAppear, pero debido a cómo SwiftUI renderiza las vistas, a menudo necesitamos un pequeño retraso para permitir que la vista construya el campo .searchable antes de intentar enfocarlo. Podemos usar .task para esto:

.task {
    // Da una fracción de segundo para que el UI se asiente
    try? await Task.sleep(nanoseconds: 100_000_000) // 0.1 segundos
    isSearchFieldFocused = true
}

Resumen de Comportamientos por Plataforma

Para tener una referencia rápida en tu próximo proyecto en Xcode, aquí tienes cómo difiere el comportamiento de searchFocused según la plataforma:

PlataformaComportamiento al activarse searchFocused = trueMétodo de entrada principal
iOSEl campo de búsqueda recibe un anillo de foco (o cursor) y el teclado virtual se desliza hacia arriba.Teclado en pantalla o físico.
macOSEl campo de búsqueda (usualmente en la Toolbar) obtiene un anillo de foco azul (o el color de acento).Teclado físico.
watchOSDespliega la interfaz nativa de entrada a pantalla completa (Scribble o Dictado).Voz (Dictado), Scribble, o Teclado.

Conclusión

El uso de searchFocused en SwiftUI representa un gran avance en cómo los desarrolladores interactúan con la interfaz de usuario de manera declarativa. Ha eliminado la necesidad de interactuar con delegados complejos y controladores de UIKit o AppKit, permitiendo que la programación Swift se mantenga limpia, concisa y fácil de mantener.

Como iOS Developer, dominar la gestión del foco no solo hace que tu código en Xcode sea más elegante, sino que impacta directamente en la calidad final de tu aplicación. Un usuario que no tiene que estirar el pulgar para tocar una pequeña barra de búsqueda, porque tú lo anticipaste y enfocaste programáticamente, es un usuario feliz.

Si tienes cualquier duda sobre este artículo, contacta conmigo y estaré encantado de ayudarte 🙂. Puedes contactar conmigo en mi perfil de X o en mi perfil de Instagram.

Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Article

Cómo añadir el botón de cerrar a una Sheet en SwiftUI

Next Article

ListStyle en SwiftUI

Related Posts