Programación en Swift y SwiftUI para iOS Developers

Cómo cambiar el color de un placeholder en un TextField en SwiftUI

¿Frustrado porque el placeholder gris por defecto arruina el diseño de tu app en SwiftUI? Aprende las técnicas modernas (iOS 15+) y clásicas para personalizar el color del texto de marcador de posición en Xcode. Tutorial paso a paso con código.


Introducción: El Arte de los Pequeños Detalles en la UI

En el vasto universo del desarrollo de aplicaciones móviles, a menudo nos centramos en las grandes arquitecturas: la gestión del estado, las llamadas a redes complejas o la navegación fluida. Sin embargo, la experiencia del usuario (UX) se define en los detalles. Un diseño impecable puede verse empañado por un elemento que no encaja del todo, un color que desentona o una falta de contraste que dificulta la lectura.

Si llevas algún tiempo desarrollando interfaces con SwiftUI en Xcode, es probable que te hayas topado con una de esas pequeñas frustraciones que parecen desproporcionadamente difíciles de resolver: cambiar el color del placeholder (marcador de posición) de un TextField.

Es una petición de diseño extremadamente común. Tu diseñador te entrega un mockup precioso con un fondo oscuro y un campo de texto donde el placeholder debe ser un blanco sutil, o quizás un color de marca específico. Abres Xcode, escribes TextField("Buscar...", text: $texto), e intentas aplicar un modificador como .placeholderColor(.red).

Y entonces te das cuenta: ese modificador no existe.

El color gris por defecto que Apple impone en los placeholders de SwiftUI es elegante, pero no es universal. A veces es demasiado claro para fondos claros, y otras veces simplemente no encaja con tu paleta de colores.

En este tutorial definitivo para tu blog tecnológico de confianza, vamos a diseccionar este problema. No solo te daremos una solución rápida; exploraremos por qué sucede esto y te enseñaremos las diferentes maneras de abordarlo, dependiendo de la versión de iOS a la que apuntes. Desde la solución moderna y nativa introducida en iOS 15 hasta los trucos clásicos utilizando ZStack para versiones anteriores, saldrás de aquí dominando este aspecto de tus formularios en SwiftUI.

¡Abre tu Xcode y empecemos!


Entendiendo el Problema: El TextField Nativo

Antes de saltar a las soluciones, es crucial entender cómo SwiftUI maneja el componente TextField.

En sus versiones iniciales, SwiftUI era famoso por ser una capa declarativa sobre los componentes de UIKit (como UITextField). UITextField en el mundo antiguo tenía una propiedad attributedPlaceholder que permitía una fácil colorización.

Sin embargo, el TextField de SwiftUI busca ser más abstracto. Cuando escribimos:

@State private var username: String = ""

var body: some View {
    TextField("Nombre de usuario", text: $username)
        .padding()
        .background(Color.gray.opacity(0.2))
        .cornerRadius(10)
}

El primer string, “Nombre de usuario”, sirve un doble propósito en las primeras versiones de SwiftUI. Actúa como la etiqueta de accesibilidad y como el texto visual del placeholder si el campo está vacío.

El instinto natural de cualquier desarrollador es intentar cambiar el color del texto usando el modificador estándar:

TextField("Nombre de usuario", text: $username)
    .foregroundColor(.red) // ¡ALERTA DE SPOILER: Esto no hace lo que crees!

Si ejecutas esto, verás que el modificador .foregroundColor(.red) cambia el color del texto que el usuario escribe, pero el placeholder sigue siendo ese gris obstinado.

Esta limitación llevó a la comunidad a crear soluciones creativas durante los primeros años de SwiftUI. Afortunadamente, Apple escuchó nuestras plegarias en la WWDC21.


Solución 1: La Manera Moderna (iOS 15 y superior)

Si tu aplicación solo necesita soportar iOS 15, macOS 12, y versiones posteriores (lo cual es cada vez más común en 2024), estás de suerte. La solución es limpia, nativa y muy potente.

La clave radica en un cambio sutil pero fundamental en los inicializadores de TextField introducidos en Swift 5.5.

Apple separó el concepto de “etiqueta” (para accesibilidad y títulos de formulario) del concepto de “prompt” (el placeholder visual). Y lo más importante: el parámetro prompt ahora acepta una vista de tipo Text, no solo un simple String.

El poder de aceptar una vista Text

El hecho de que el placeholder sea ahora una vista Text significa que podemos aplicarle modificadores de texto estándar antes de pasarlo al TextField.

Veamos la firma del inicializador moderno:

// Firma simplificada
TextField(text: Binding<String>, prompt: Text?) {
    // Label View
}

O la versión más común que usa un título visible:

TextField(_ titleKey: LocalizedStringKey, text: Binding<String>, prompt: Text?)

Ese parámetro prompt: Text? es nuestra puerta de entrada a la personalización.

Implementación Paso a Paso

Vamos a crear una vista simple con un campo de texto que tenga un placeholder de color azul personalizado.

Paso 1: Configuración básica

Abre un nuevo proyecto de SwiftUI en Xcode. Asegúrate de que el “Deployment Target” sea iOS 15.0 o superior.

import SwiftUI

struct ModernPlaceholderView: View {
    @State private var email: String = ""

    var body: some View {
        VStack {
            Text("Formulario Moderno")
                .font(.headline)
            
            // Aquí irá nuestro TextField
        }
        .padding()
    }
}

Paso 2: Usando el nuevo inicializador

En lugar de usar el inicializador clásico TextField("Placeholder", text: $...), vamos a ser explícitos con el parámetro prompt.

TextField("Email", text: $email, prompt: Text("Introduce tu correo..."))
    .textFieldStyle(.roundedBorder)

Si ejecutas esto, todavía verás el gris por defecto. Hemos cambiado la forma de declararlo, pero no el estilo.

Paso 3: Aplicando el color al prompt

Aquí viene la magia. Como el prompt es una vista Text, podemos aplicarle .foregroundColor (o el más moderno .foregroundStyleen iOS 17) directamente a ese objeto Text.

import SwiftUI

struct ModernPlaceholderView: View {
    @State private var email: String = ""

    // Definimos un color personalizado para el ejemplo
    let placeholderColor = Color.blue.opacity(0.6)

    var body: some View {
        VStack(spacing: 20) {
            Text("Solución iOS 15+")
                .font(.title2)
                .fontWeight(.bold)
            
            TextField(
                "Email Label (Accesibilidad)", // Esto no se ve como placeholder
                text: $email,
                prompt: Text("Escribe tu email aquí...")
                        .foregroundColor(placeholderColor) // ¡Aquí está el truco!
                        .font(.system(size: 16, weight: .light, design: .serif)) // Incluso podemos cambiar la fuente
            )
            .padding()
            .background(Color.gray.opacity(0.1))
            .cornerRadius(12)
            .overlay(
                RoundedRectangle(cornerRadius: 12)
                    .stroke(Color.blue, lineWidth: 1)
            )
            
            Text("Texto escrito: \(email)")
                .font(.caption)
        }
        .padding()
    }
}

#Preview {
    ModernPlaceholderView()
}

Análisis del Código:

Observa la línea clave: prompt: Text("...").foregroundColor(...).

Estamos creando una vista Text, la estamos modificando para que sea azul y tenga una fuente específica, y luego estamos entregando esa vista ya estilizada al TextField para que la use cuando no haya texto escrito por el usuario.

Esta es, sin duda, la forma recomendada y más eficiente de lograr este objetivo en el desarrollo actual de SwiftUI. Es semánticamente correcta y aprovecha el sistema de tipos de Swift.


Solución 2: El Enfoque Clásico con ZStack (Para compatibilidad retroactiva)

¿Qué pasa si tu jefe o cliente te dice que la aplicación debe soportar iOS 14 o incluso iOS 13? La solución moderna de prompt: Text no compilará.

En este escenario, debemos recurrir al ingenio de la comunidad. La técnica más robusta antes de iOS 15 implicaba “engañar” visualmente al usuario superponiendo elementos.

La lógica es la siguiente: si el TextField no nos deja cambiar su placeholder, no usaremos su placeholder. Dejaremos el placeholder nativo vacío y, en su lugar, colocaremos una vista Text nuestra justo encima del TextField usando un ZStack.

El truco está en mostrar nuestra vista Text solo cuando la variable de estado (el texto que escribe el usuario) esté vacía.

Implementación Paso a Paso (Estilo Clásico)

Paso 1: La estructura ZStack

Necesitamos un ZStack que contenga nuestro placeholder personalizado en el fondo y el TextField real en el frente. Es importante que el ZStack tenga alineación .leading (izquierda) para que los textos coincidan.

import SwiftUI

struct ClassicPlaceholderView: View {
    @State private var search text: String = ""
    
    var body: some View {
        ZStack(alignment: .leading) {
            // Capa 1: El Placeholder Falso
            // Capa 2: El TextField Real
        }
        .padding()
    }
}

Paso 2: Añadiendo la lógica condicional

Vamos a implementar las dos capas. El placeholder falso solo debe ser visible si searchText.isEmpty es verdadero.

import SwiftUI

struct ClassicPlaceholderView: View {
    @State private var searchText: String = ""
    
    var body: some View {
        VStack(spacing: 25) {
            Text("Solución Clásica (iOS 13-14)")
                .font(.headline)
            
            // El contenedor del campo de texto
            ZStack(alignment: .leading) {
                
                // CAPA TRASERA: Nuestro placeholder personalizado
                if searchText.isEmpty {
                    Text("Buscar productos...")
                        .foregroundColor(.orange) // ¡El color que queríamos!
                        .font(.body) // Importante: que coincida con la fuente del TextField
                        .padding(.leading, 4) // Un pequeño ajuste para alinear perfectamente
                }
                
                // CAPA DELANTERA: El TextField funcional
                TextField("", text: $searchText)
                    .foregroundColor(.black) // Color del texto que escribe el usuario
                    .font(.body)
            }
            .padding()
            .background(Color.orange.opacity(0.1))
            .cornerRadius(8)
        }
        .padding()
    }
}

#Preview {
    ClassicPlaceholderView()
}

Análisis y Desventajas de este método:

Este método funciona perfectamente en versiones antiguas de iOS, pero tiene sus inconvenientes (“gotchas”):

  1. Alineación Frágil: Tienes que asegurarte manualmente de que el tamaño de fuente y el padding del Textsuperpuesto coincidan exactamente con los del TextField nativo. Si no, el texto “saltará” cuando el usuario empiece a escribir. Observa el .padding(.leading, 4) que tuve que añadir en el ejemplo; ese valor puede variar según el estilo del TextField.
  2. Código más Verboso: Requiere más líneas de código y lógica condicional en tu vista principal.
  3. Accesibilidad: Debes tener cuidado. Al usar TextField("", text:...), estás dejando el campo sin etiqueta para VoiceOver. Deberías añadir explícitamente .accessibilityLabel("Buscar productos") al TextField.

Nivel Experto: Creando un Modificador Reutilizable

Si estás trabajando en una aplicación grande, no querrás estar copiando y pegando ZStacks por todas partes. La belleza de SwiftUI es su capacidad de composición.

Vamos a tomar la solución clásica (la del ZStack, ya que es la más compleja de escribir repetidamente) y empaquetarla en un ViewModifier elegante. Esto nos permitirá usar una sintaxis limpia como .customPlaceholder(...) en cualquier TextField.

import SwiftUI

// 1. Definimos el ViewModifier
struct CustomPlaceholderModifier: ViewModifier {
    var placeholderText: String
    var placeholderColor: Color
    // Necesitamos saber el estado del texto para ocultar/mostrar
    @Binding var text: String
    var font: Font = .body

    func body(content: Content) -> some View {
        ZStack(alignment: .leading) {
            if text.isEmpty {
                Text(placeholderText)
                    .foregroundColor(placeholderColor)
                    .font(font)
                    .padding(.leading, 4) // Ajuste fino de alineación
            }
            content
                .font(font) // Aseguramos que el TextField tenga la misma fuente
        }
    }
}

// 2. Extendemos View para hacerlo fácil de usar
extension View {
    func customPlaceholder(
        _ text: String,
        color: Color,
        binding: Binding<String>,
        font: Font = .body
    ) -> some View {
        self.modifier(CustomPlaceholderModifier(
            placeholderText: text,
            placeholderColor: color,
            text: binding,
            font: font
        ))
    }
}

// 3. Uso en una vista real
struct ReusableModifierExample: View {
    @State private var comentario: String = ""

    var body: some View {
        VStack {
            Text("Usando ViewModifier Reutilizable")
                .font(.title3)
            
            TextField("", text: $comentario)
                // ¡Mira qué limpio queda ahora!
                .customPlaceholder(
                    "Escribe tu comentario aquí...",
                    color: .purple.opacity(0.7),
                    binding: $comentario,
                    font: .system(size: 18, design: .rounded)
                )
                .padding()
                .background(Color.purple.opacity(0.05))
                .cornerRadius(15)
        }
        .padding()
    }
}

#Preview {
    ReusableModifierExample()
}

Con este enfoque, encapsulamos la complejidad del ZStack y la lógica condicional. Ahora, cualquier desarrollador del equipo puede aplicar un placeholder de color con una sola línea de código, manteniendo las vistas limpias y legibles.


Conclusión

Cambiar el color del placeholder en un TextField de SwiftUI es el ejemplo perfecto de cómo ha madurado el framework. Lo que antes requería trucos de superposición y gestión manual del estado, ahora se resuelve de forma elegante y nativa en las versiones modernas de iOS.

Resumen de la estrategia:

  • ¿Tu app requiere iOS 15+? Usa la solución moderna: TextField(..., prompt: Text("...").foregroundColor(.tuColor)). Es la mejor opción, la más limpia y la que menos errores produce.
  • ¿Debes soportar iOS 14 o inferior? No tienes otra opción que usar la técnica del ZStack superponiendo un Textcondicional. Si lo haces, considera seriamente crear un ViewModifier reutilizable para mantener tu código sano.

Como desarrolladores de SwiftUI, es vital conocer tanto las herramientas nuevas como las técnicas antiguas. Nunca sabes cuándo tendrás que mantener un proyecto legacy o cuándo podrás disfrutar de las últimas APIs de Apple.

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 instalar Swift en Windows

Related Posts