¿Qué es TextField
?
El componente TextField
es un campo de texto editable por el usuario. Será usado para que el usuario pueda introducir datos que la aplicación podrá leer. Es un componente equivalente a UITextField
de UIKit
.
Aquí podéis consultar la documentación oficial
El componente tiene varios 'inicializadores' con diferentes parámetros y propósitos. A nivel más básico, se crea de la siguiente forma:
TextField("Placeholder", text: .constant(""))
Nota: el parámetro
text
es una variable de estado que usará el componenteTextField
para asignar el valor que se escriba. Cuando no queremos controlar el valor del campo de texto (por ejemplo por motivos de pruebas como este caso) podemos usar la función.constant()
que pertenece al structBinding
para cumplir con su implementación sin tener control sobre el dato.
Cómo leer datos de un TextField
En un caso real el valor del parámetro text
deberá estar asociado a una variable de estado del propio struct
de la vista para que así podamos tener control y consultar el dato del TextField
.
struct ContentView: View {
@State var inputText: String = ""
var body: some View {
Group {
TextField("Write something", text: $inputText)
}
}
}
De esta forma cada vez que el usuario escriba algo en el campo de texto se modificará la variable de estado inputText
, provocando que cualquier parte del código que dependa de esta variable se refresque para controlar el nuevo estado. Por ejemplo, vamos a mostrar un Text
cuando el usuario haya escrito en el TextField
y este Text
tendrá el contenido del TextField
.
struct ContentView: View {
@State var inputText: String = ""
var body: some View {
Group {
TextField("Write something", text: $inputText)
.frame(height: 44)
.textFieldStyle(RoundedBorderTextFieldStyle())
if !inputText.isEmpty {
Text("User say: " + inputText)
}
}
}
}

Modificadores comunes para TextField
A parte de los modificadores que se explicarán a continuación, el componente TextField
comparte los mismos métodos de personalización que el componente View
y pueden ser consultados en el siguiente enlace.
textFieldStyle
Permite indicar el estilo del TextField
. El parámetro pasado debe implementar el protocolo TextFieldStyle
.
Por defecto existen las siguientes implementaciones (para iOS):
DefaultTextFieldStyle
. Este estilo varía dependiendo de la plataforma. En el caso de iOS se aplica el estiloPlainTextFieldStyle
.PlainTextFieldStyle
. Estilo de texto plano y sin bordes. A simple vista se vería como unText
.RoundedBorderTextFieldStyle
. Estilo del sistema. Tiene bordes redondeados.
background
Permite añadir una vista al fondo del componente.
TextField("Background", text: .constant(""))
.padding()
.background(Color.yellow)
.clipShape(RoundedRectangle(cornerRadius: 8))

foreground
Modifica el color del texto. No afecta al placeholder.
TextField("Foreground placeholder", text: .constant("Text foreground"))
.padding()
.foregroundColor(.blue)

font
Permite modificar la fuente del texto. No afecta al placeholder.
TextField("Font placeholder", text: .constant("Text font"))
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.font(.caption2)

textContentType
Permite indicar el tipo de contenido del TextField
para que se activen sugerencias de texto relacionadas. Funciona a través del aprendizaje del propio teclado de iOS.
TextField("Email placeholder", text: .constant(""))
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.textContentType(.emailAddress)
keyboardType
Permite modificar el tipo de teclado a mostrar cuando se edita el campo de texto.
TextField("keyboardType numberPad", text: .constant(""))
.keyboardType(.numberPad)
TextField("keyboardType emailAddress", text: .constant(""))
.keyboardType(.emailAddress)

autocapitalization
Permite indicar cómo se debe comportar el teclado con respecto a las mayúsculas automáticas.
TextField("Autocapitalization allCharacters", text: .constant(""))
.autocapitalization(.allCharacters)
TextField("Autocapitalization words", text: .constant(""))
.autocapitalization(.words)

Cómo añadir un borde personalizado
Para añadir un borde personalizado usaremos la combinación de los modificadores overlay
y clipShape
.
TextField("Background", text: .constant(""))
.padding()
.background(Color.yellow)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.blue, lineWidth: 2))
.clipShape(RoundedRectangle(cornerRadius: 10))

En este caso overlay
pintará el borde que queramos y clipShape
limitará el frame del componente para que no se pinte el background
fuera de los bordes.
Cómo modificar el placeholder
Actualmente no hay forma de modificar el estilo del placeholder del componente, por lo que el componente no se adapta a todas las situaciones que podríamos requerir en un proyecto.
La forma de solucionar este problema es crear un ViewModifier
que nos permita realizar modificaciones sobre el propio TextField
para incluirle una vista personalizada que simulará un placeholder.
Para ello, crearemos un nuevo struct
que implemente el protocolo ViewModifier
.
struct CustomPlaceholder<T: View>: ViewModifier {
var placeholder: T
var show: Bool
func body(content: Content) -> some View {
ZStack(alignment: .leading) {
if show {
placeholder
}
content
}
}
}
Para usarlo deberemos usar el método modifier
sobre el propio TextView
para 'setear' una instancia de CustomPlaceholder
. Para hacerlo más fácil crearemos una extensión de View
que aplique este cambio de forma más resumida:
extension View {
func placeholder<T: View>(_ view: T, show: Bool) -> some View {
self
.modifier(CustomPlaceholder(placeholder: view, show: show))
}
}
Como resultado solo tendremos que llamar al método placeholder
sobre nuestro componente TextView
y asegurarnos de dejar el placeholder
original del TextView
vacío.
TextField("", text: $customPlaceholderText1)
.padding()
.foregroundColor(.blue)
.placeholder(Text("Custom placeholder 1").padding().foregroundColor(.purple), show: customPlaceholderText1.isEmpty)

TextField("", text: $customPlaceholderText2)
.padding()
.foregroundColor(.white)
.placeholder(Text("Custom placeholder 2").padding().foregroundColor(.orange), show: customPlaceholderText2.isEmpty)
.background(Color.black)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.red, lineWidth: 3))
.clipShape(RoundedRectangle(cornerRadius: 10))

Truco: Este
placeholder
no es exclusivo de unTextField
. Se puede usar para cualquier componente, como por ejemplo para poner una imagen mientras se carga la imagen real desde una url.
Cómo controlar la entrada de datos de un TextField
El componente TextField
recibe una variable de estado donde podemos recoger los datos que escribe el usuario. Hay ocasiones en las que necesitaremos controlar lo que escribe el usuario en el mismo momento en el que escribe, como por ejemplo cuando el TextField
solo acepta valores numéricos o tiene una longitud máxima.
Para controlar la entrada de datos debemos hacer uso de Combine
que nos permitirá conocer los cambios que se realizan sobre la variable de estado asociada al TextField
.
TextField
con valores numéricos
Para este caso podemos pensar que solo es necesario cambiar el tipo de teclado del TextField
con el modificador .numberPad
.
@State var onlyNumbersValue: String = ""
...
TextField("Only numbers", text: $onlyNumbersValue)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
Si bien este es un primer paso, cambiar el tipo de teclado no limita los caracteres que el usuario puede introducir. Si nosotros ejecutamos este ejemplo en un simulador, podemos escribir con el teclado todos los caracteres que queramos, y lo mismo se podría hacer en un dispositivo sin se conecta un teclado externo.
Para realizar el control correcto debemos escuchar los cambios de la variable onlyNumbersValue
para modificarla si no se cumple la condición que definamos, que en este caso es que solo contenga valores numéricos.
import SwiftUI
import Combine
@State var onlyNumbersValue: String = ""
...
TextField("Only numbers", text: $onlyNumbersValue)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
.onReceive(Just(onlyNumbersValue)) { value in
let filtered = "\(value)".filter { "0123456789".contains($0) }
if filtered != value {
self.onlyNumbersValue = "\(filtered)"
}
}

*Hemos desactivado el teclado de tipo numérico para poder ver el comportamiento en el ejemplo.
TextField
con una longitud máxima
Para limitar el TextField
a una longitud máxima vamos a escuchar los cambios de la variable de estado de tipo String donde se almacena el texto (maxLenghtValue
), y vamos a cortar el texto a la longitud deseada:
import SwiftUI
import Combine
@State var maxLenghtValue: String = ""
...
TextField("Max Lenght (10 chars)", text: $maxLenghtValue)
.textFieldStyle(RoundedBorderTextFieldStyle())
.onReceive(Just(maxLenghtValue)) { value in
let shortString = String(value.prefix(10))
if shortString != value {
self.maxLenghtValue = shortString
}
}

Cómo mostrar un campo de texto seguro
En UIKit
el componente UITextField
tenía el parámetro secureTextEntry
para indicar que el campo de texto debía ocultar los caracteres. Esto se usa comúnmente para los campos de tipo contraseña.
En SwiftUI
para conseguir el mismo comportamiento no existe un modificador sobre TextField
. En su lugar se usa otro componente: SecureField
.
SecureField
El componente SecureField
se comporta de la misma forma que un TextField
, pero el contenido que se escriba quedará oculto. Cualquier comportamiento que podamos aplicar a un TextField
también podrá ser aplicado a un SecureField
.

Cómo ocultar el teclado
Hay ocasiones en las que es necesario ocultar el teclado si lo estamos mostrando, ya que hemos pulsado un botón que implica la finalización de la toma de datos.
Para estos casos SwiftUI
no incluye una forma de ocultar el teclado, pero podemos crearnos nuestro propio código con esta funcionalidad.
En este caso vamos a realizar una extensión de View
con la implementación necesaria para ocultar el teclado:
import SwiftUI
import UIKit
extension View {
func hideKeyboard() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
Para usarlo solo tenemos que invocar al método hideKeyboard
cuando queramos ocultar el teclado.
Vamos a ver dos casos que suelen ser útiles:
Al pulsar un botón
En este caso tenemos que invocar al método cuando entre por el evento de pulsación del botón:
struct ContentView: View {
var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 15) {
TextField("Hide keyboard", text: .constant(""))
.textFieldStyle(RoundedBorderTextFieldStyle())
Button("Done!") {
self.hideKeyboard()
}
}
}.navigationBarTitle("TextField Style", displayMode: .inline)
.padding()
}
}
}

Al pulsar en la pantalla
Para este caso debemos implementar el método onTapGesture
de la vista que pueda ocultar el teclado cuando pulsemos sobre ella. Normalmente será la vista que contenga a todos los elementos de la pantalla. Si esa vista es transparente se necesitará también el modificador contentShape
para que se pueda recibir correctamente el evento de tap
sobre la vista (consultar la documentación de View
para más información).
struct ContentView: View {
var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 15) {
TextField("Hide keyboard", text: .constant(""))
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}.navigationBarTitle("TextField Style", displayMode: .inline)
.padding()
.contentShape(Rectangle())
.onTapGesture {
self.hideKeyboard()
}
}
}
}

Ejemplo
Puedes encontrar este ejemplo en github.com bajo el apartado TextField.