Alexandre Freire

Blog sobre desarrollo en Swift, iOS y Xcode

¿Cómo crear un UITextField personalizado?

Feb 24, 2020

Los UITextField son feos. Horrorosos. Muy pocas apps con un diseñador detrás usa los campos de texto por defecto.

En este tutorial te voy a enseñar como crear un «underlined» UITextField como los que ves en la siguiente imagen. Un UITextField personalizado, sin borders y con una línea debajo.

Únete a la newsletter para no perderte ningún nuevo tutorial: recibe un email cuando se publiquen nuevos artículos o vídeos y no pierdas la oportunidad de seguir aprendiendo. 

Paso 1: Crear una subclase de UITextField

Abrimos Xcode y creamos una subclase de UITextField. Ésta tendrá una propiedad privada que representará la línea inferior de subrayado (el underline), de tipo CALayer:

class UnderlinedtextField: UITextField {
    
    private let underline = CALayer()

}

Paso 2: Configurar la propiedad underline

Ahora vamos a configurar el estilo de la línea. Propiedades como su color o su grosor, así como su posición relativa al UITextField. Para ello, nos creamos una función llamada setupUnderline().

private func setupUnderline() {
    // 1
    borderStyle = .none
        
    // 2
    let lineWidth: CGFloat = 1.0
    underline.borderColor = UIColor.darkGray.cgColor
    underline.frame = CGRect(
        x: 0,
        y: frame.size.height - lineWidth,
        width: frame.size.width,
        height: frame.size.height
    )
    underline.borderWidth = lineWidth
        
    // 3
    layer.addSublayer(underline)
    layer.masksToBounds = true
}

¡Guau! Mucho código hay aquí. Vamos por partes:

  • // 1: Borramos los horrorosos bordes que tienen los UITextField por defecto.
  • // 2: Configuramos el estilo de la línea. Le asignamos un color, un grosor, y en dónde debe situarse.
  • // 3: Añadimos la línea a la pila de capas (layer stack)

layer.addSublayer(underline) sólo añadirá una sola capa, ya que se trata siempre de la misma propiedad underline. Es decir, aunque este método se llamase 100 veces, sólo tendríamos una capa añadida.

Paso 3: Sobreescrir setNeedsLayout

Vamos a «sobreescribir» este método para decirle a la app que tiene que actualizar el layout por defecto de los UITextFields. Técnicamente, si llamamos a este método se invalida el layout actual y avisa que se tiene que modificar durante el siguiente ciclo de actualización de la vista. Si quieres más información, échale un ojo a la documentación de UIKit.

override func setNeedsLayout() {
    super.setNeedsLayout()
    setupUnderline()
}

Si ejecutamos la aplicación en estos momentos, veríamos algo así:

Paso 4: Modificar el intrinsicContentSize

Ahora mismo, la línea está muy cerca de lo que es el texto en sí. Cualquier diseñador te diría que los campos de texto «no respiran». Eso, traducido a nuestro idioma es que necesitamos añadir un poco de espacio entre la línea y el texto.

Hay muchas maneras de hacer esto, pero ya que estamos creando una subclase que puede ser utilizada tanto desde el InterfaceBuilder como por código, lo más limpio (en mi opinión) es modificar el intrinsicContentSize de nuestra clase. Añade este código debajo de la propiedad underline.

override var intrinsicContentSize: CGSize {
    return CGSize(width: UIView.noIntrinsicMetric, height: 35.0)
}

Por defecto, la altura del intrinsicContentSize de un UITextField es 34. Dado que el grosor de la línea es 1, como mínimo deberíamos añadir 1, de ahí el 35.

Si volvemos a ejecutar la aplicación, tu diseñador estaría algo más contento:

Pero claro, a los diseñadores les gusta que los componentes «respiren por todos los lados». Actualmente, sólo respira por abajo.

Paso 5: Añadir padding por los lados

Actualmente, si el usuario empieza a escribir el texto aparece inmediatamente encima de la línea, sin ningún margen interior. A los diseñadores esto tampoco les gusta, así que te exige que el texto «respire» también por los lados. Para ello, vamos a crear una función addPadding()

private func addPadding() {
    // 1 
    rightViewMode = .always
    leftViewMode = .always

    // 2
    let paddingView = UIView(
        frame: CGRect(
            x: 0,
            y: 0,
            width: 12,
            height: 35
        )
    )

    // 3
    leftView = paddingView
    rightView = paddingView
}

El padding consistirá simplemente en una vista vacía. Vayamos por partes:

  • // 1: Todos los UITextFields tienen un contenedor de vistas tanto a la izquierda como a la derecha. Tenemos que activar estos contenedores a través de rightViewMode y leftViewMode, asignando su valor a .always
  • // 2: Creamos una vista de 12px de ancho, y el mismo alto del UITextField. Probablemente sería bueno extraer la altura en una constante.
  • // 3: Asignamos el paddingView a la vista de la izquierda y de la derecha.

Probablemente lo siguiente que haríamos todos (yo incluido) es llamar a esta función dentro de setNeedsLayout(), justo después de setupUnderline(). Si ejecutamos esta función dentro, provocaríamos un bucle infinito, ya que la super clase UIView llama a setNeedsLayout() por debajo cuando modificamos su layout, y eso es precisamente lo que estamos haciendo cuando utilizamos rightView o leftView.

Tenemos que ejecutar esta función desde otro sitio

Paso 6: Crear los inicializadores (inits)

Debajo de la propiedad intrinsicContentSize añade los métodos init. Después de llamar a super, configuramos el padding:

override init(frame: CGRect) {
    super.init(frame: frame)
    addPadding()
}

required init?(coder: NSCoder) {
    super.init(coder: coder)
    addPadding()
}

Si ejecutamos la aplicación, ahora se vería así:

Bonus: Ver el UnderlinedTextField desde .xib o Storyboards

Para utilizar el UnderlinedTextField desde un fichero .xib o un Storyboard, basta con añadir un UITextField por defecto y cambiarle la clase. Sin embargo, en el Storyboard se verían los text fields por defecto. Y son feos. MUY feos.

Después de asignar la clase de todos los UITextField como UnderlinedTextField, volvemos al fichero .swift y añadimos lo siguiente:

@IBDesignable & prepareForInterfaceBuilder()

Para poder ver los estilos del text field personalizado en el InterfaceBuilder, debemos declararla como @IBDesignable e implementar el método prepareForInterfaceBuilder(), donde llamaremos a addPading() y setupUnderline() :

@IBDesignable
class UnderlinedtextField: UITextField {
  
    ...
    ... 

    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        addPadding()
        setupUnderline()
    }
}

Ahora el text field personalizado se verá en los Storyboards o .xibs:

Conclusión

Crear tu propia subclase de UITextField y poder utilizarla, tanto desde InterfaceBuilder como por código no es tan difícil. Por fin podrás deshacerte de los horribles text field nativos y utilizar los tuyos personalizados.

Si tienes alguna duda o sugerencia, escríbeme un comentario aquí abajo.

Por último, si te ha resultado útil te agradecería mucho que compartieras el post en tus redes sociales.

¿Me ayudas a compartir en redes sociales?
Share on Facebook
Facebook
Pin on Pinterest
Pinterest
Tweet about this on Twitter
Twitter
Share on LinkedIn
Linkedin
Buffer this page
Buffer
Share on Reddit
Reddit

2 Comentarios

  1. Jetro Tortolero

    Buenas tardes.
    Como se haría esto mismo en SWIFUI.
    Saludos.

    Responder
    • Alexandre Freire

      Hola Jetro,

      Gracias por tu pregunta. En SwiftUI bastaría con añadir un TextField y un Rectangle en una VStack


      struct UnderlinedTextField: View {
      let title: String
      @Binding var text: String
      var body: some View {
      VStack {
      TextField(title, text: $text)
      Rectangle()
      .foregroundColor(.gray)
      .frame(height: 1)
      }
      }
      }

      Responder

Enviar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *