Hola Habr! Le presento la traducción del artículo
Creación de elementos de interfaz de usuario mediante programación mediante PureLayout por Aly Yaka.

Bienvenido a la segunda parte del artículo sobre la creación programática de una interfaz usando PureLayout.
En la primera parte, creamos la interfaz de usuario de una aplicación móvil simple completamente en código, sin usar Storyboards o NIB. En esta guía, cubriremos algunos de los elementos de interfaz de usuario más utilizados en todas las aplicaciones:
- UINavigationController / Bar
- UITableView
- UITableViewCell de tamaño propio
UINavigationController
En nuestra aplicación, probablemente necesite una barra de navegación para que el usuario pueda pasar de la lista de contactos a información detallada sobre un contacto específico y luego regresar a la lista.
UINavigationController
puede resolver fácilmente este problema utilizando la barra de navegación.
UINavigationController
es solo una pila donde mueves muchas vistas. El usuario ve la vista superior (la que se movió por última vez) en este momento (excepto cuando se presenta otra vista además de esto, digamos favoritos). Y cuando presiona los controladores de vista superior del controlador de navegación, el controlador de navegación crea automáticamente un botón "atrás" (lado superior izquierdo o derecho dependiendo de las preferencias de idioma actuales del dispositivo), y presionar este botón lo regresa a la vista anterior.
Todo esto se maneja fuera de la caja por el controlador de navegación. Y agregar uno más solo tomará una línea de código adicional (si no desea personalizar la barra de navegación).
Vaya a AppDelegate.swift y agregue la siguiente línea de código a continuación, deje
viewController = ViewController ():
let navigationController = UINavigationController(rootViewController: viewController)
Ahora cambie
self.window? .RootViewController = viewController self.window? .RootViewController = navigationController
self.window? .RootViewController = viewController self.window? .RootViewController = navigationController
self.window? .RootViewController = viewController self.window? .RootViewController = navigationController
. En la primera línea, creamos una instancia de
UINavigationController
y le pasamos nuestro
viewController
como
rootViewController
, que es el controlador de vista en la parte inferior de la pila, lo que significa que nunca habrá un botón de retroceso en la barra de navegación de esta vista. Luego le damos a nuestra ventana un controlador de navegación como
rootViewController
, porque ahora contendrá todas las vistas en la aplicación.
Ahora ejecuta tu aplicación. El resultado debería verse así:
Lamentablemente, algo salió mal. Parece que la barra de navegación se superpone a nuestro upperView, y tenemos varias formas de solucionarlo:
- Aumente el tamaño de nuestro
upperView
para que se ajuste a la altura de la barra de navegación. - Establezca la propiedad
isTranslucent
de la barra de navegación en false
. Esto hará que la barra de navegación sea opaca (si no ha notado que es un poco transparente), y ahora el borde superior de la supervista se convertirá en la parte inferior de la barra de navegación.
Yo personalmente elegiré la segunda opción, pero estudiarás la primera. También recomiendo revisar y leer cuidadosamente los documentos de Apple en
UINavigationController
y
UINavigationBar
:
Ahora ve al método viewDidLoad y agrega esta línea
self.navigationController? .NavigationBar.isTranslucent = false super.viewDidLoad ()
self.navigationController? .NavigationBar.isTranslucent = false super.viewDidLoad ()
, para que se vea así:
override func viewDidLoad() { super.viewDidLoad() self.navigationController?.navigationBar.isTranslucent = false self.view.backgroundColor = .white self.addSubviews() self.setupConstraints() self.view.bringSubview(toFront: avatar) self.view.setNeedsUpdateConstraints() }
También puede agregar esta línea
self.title = "John Doe" viewDidLoad
, que agregará un "Perfil" a la barra de navegación para que el usuario sepa dónde se encuentra actualmente. Ejecute la aplicación y el resultado debería verse así:
Refactorizando nuestro controlador de vista
Antes de continuar, necesitamos reducir nuestro archivo
ViewController.swift
que solo podamos usar lógica real, no solo código para elementos de la interfaz de usuario. Podemos hacer esto creando una subclase de
UIView
y moviendo todos nuestros elementos de interfaz de usuario allí. La razón por la que hacemos esto es seguir el Modelo-Vista-Controlador o el patrón arquitectónico MVC para abreviar. Obtenga más información sobre MVC
Model-View-Controller (MVC) en iOS: un enfoque moderno .
Ahora haga clic derecho en la carpeta
ContactCard
en Project Navigator y seleccione "Nuevo archivo":

Haga clic en Cocoa Touch Class y luego en Siguiente. Ahora escriba "ProfileView" como el nombre de la clase, y al lado de "Subclase de:" asegúrese de ingresar "UIView". Simplemente le dice a Xcode que haga que nuestra clase herede
UIView
de
UIView
, y agregará un código repetitivo. Ahora haga clic en Siguiente, luego cree y elimine el código comentado:
Y ahora estamos listos para refactorizar.
Corte y pegue todas las variables perezosas del controlador de vista en nuestra nueva vista.
Debajo de la última variable pendiente, anule
init(frame :)
escribiendo
init
y luego seleccionando el primer resultado de autocompletar de Xcode.

Aparecerá un error que indica que el inicializador "requerido" "init (codificador :)" debe ser proporcionado por una subclase de "UIView":

Puede arreglar esto haciendo clic en el círculo rojo y luego en Reparar.

En cualquier inicializador anulado, casi siempre debe llamar al inicializador de superclase, así que agregue esta línea de código en la parte superior del método:
super.init (frame: frame)
.
Corte y pegue el método
addSubviews()
debajo de los inicializadores y elimine
self.view
antes de cada llamada a
addSubview
.
func addSubviews() { addSubview(avatar) addSubview(upperView) addSubview(segmentedControl) addSubview(editButton) }
Luego llame a este método desde el inicializador:
override init(frame: CGRect) { super.init(frame: frame) addSubviews() bringSubview(toFront: avatar) }
Para restricciones, anule
updateConstraints()
y agregue una llamada al final de esta función (donde siempre permanecerá):
override func updateConstraints() {
Al anular cualquier método, siempre es útil verificar su documentación visitando los documentos de Apple o, más simplemente, manteniendo presionada la tecla Opción (o Alt) y haciendo clic en el nombre de la función:

Corte y pegue el código de restricción del controlador de vista en nuestro nuevo método:
override func updateConstraints() { avatar.autoAlignAxis(toSuperviewAxis: .vertical) avatar.autoPinEdge(toSuperviewEdge: .top, withInset: 64.0) upperView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .bottom) segmentedControl.autoPinEdge(toSuperviewEdge: .left, withInset: 8.0) segmentedControl.autoPinEdge(toSuperviewEdge: .right, withInset: 8.0) segmentedControl.autoPinEdge(.top, to: .bottom, of: avatar, withOffset: 16.0) editButton.autoPinEdge(.top, to: .bottom, of: upperView, withOffset: 16.0) editButton.autoPinEdge(toSuperviewEdge: .right, withInset: 8.0) super.updateConstraints() }
Ahora regrese al controlador de vista e inicialice la instancia de
ProfileView
sobre el método
let profileView = ProfileView(frame: .zero)
, lo agregue como una subvista al
ViewController
.
¡Ahora nuestro controlador de vista se ha reducido a unas pocas líneas de código!
import UIKit import PureLayout class ViewController: UIViewController { let profileView = ProfileView(frame: .zero) override func viewDidLoad() { super.viewDidLoad() self.navigationController?.navigationBar.isTranslucent = false self.title = "Profile" self.view.backgroundColor = .white self.view.addSubview(self.profileView) self.profileView.autoPinEdgesToSuperviewEdges() self.view.layoutIfNeeded() } }
Para asegurarte de que todo funcione según lo previsto, inicia tu aplicación y comprueba cómo se ve.
Tener un controlador de revisión delgado y ordenado siempre debe ser su objetivo. Esto puede llevar mucho tiempo, pero le ahorrará problemas innecesarios durante el mantenimiento.
UITableView
A continuación, agregaremos un UITableView para presentar información de contacto como número de teléfono, dirección, etc.
Si aún no lo ha hecho, diríjase a la documentación de Apple para ver UITableView, UITableViewDataSource y UITableViewDelegate.
Vaya a
ViewController.swift
y agregue
lazy var
para
tableView
arriba
viewDidLoad()
:
lazy var tableView: UITableView = { let tableView = UITableView() tableView.translatesAutoresizingMaskIntoConstraints = false tableView.delegate = self tableView.dataSource = self return tableView }()
Si intenta ejecutar la aplicación, Xcode se quejará de que esta clase no es un delegado ni una fuente de datos para
UITableViewController
y, por lo tanto, agregaremos estos dos protocolos a la clase:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { . . .
Una vez más, Xcode se quejará de una clase que no se ajusta al protocolo
UITableViewDataSource
, lo que significa que hay métodos obligatorios en este protocolo que no están definidos en la clase. Para averiguar cuál de estos métodos debe implementar mientras mantiene Cmd + Control, haga clic en el protocolo
UITableViewDataSource
en la definición de clase y pasará a la definición de protocolo. Para cualquier método que no esté precedido por la palabra
optional
, se debe implementar una clase correspondiente a este protocolo.
Aquí tenemos dos métodos que debemos implementar:
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
- este método le dice a la vista de tabla cuántas filas queremos mostrar.public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
: este método consulta la celda en cada fila. Aquí inicializamos (o reutilizamos) la celda e insertamos la información que queremos mostrar al usuario. Por ejemplo, la primera celda mostrará el número de teléfono, la segunda celda mostrará la dirección, y así sucesivamente.
Ahora regrese a
ViewController.swift
, comience a escribir
numberOfRowsInSection
, y cuando aparezca el autocompletado, seleccione la primera opción.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { <#code#> }
Elimine el código de la palabra y regrese ahora 1.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 }
Bajo esta función, comience a escribir
cellForRowAt
y seleccione el primer método de autocompletar nuevamente.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { <#code#> }
Y, de nuevo, por ahora, devuelva un
UITableViewCell
.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return UITableViewCell() }
Ahora, para conectar nuestra vista de tabla dentro de
ProfileView
, definiremos un nuevo inicializador que tome la vista de tabla como parámetro, para que pueda agregarla como una subvista y establecer las restricciones correspondientes.
Vaya a
ProfileView.swift
y agregue el atributo para la vista de tabla justo encima del inicializador:
var tableView: UITableView!
determinado, por lo tanto, no estamos seguros de que sea constante.
Ahora reemplace la antigua implementación
init (frame :)
con:
init(tableView: UITableView) { super.init(frame: .zero) self.tableView = tableView addSubviews() bringSubview(toFront: avatar) }
Xcode ahora se quejará de la falta de
init (frame :)
para
ProfileView
, así que regrese a
ViewController.swift
y reemplace
let profileView = ProfileView (frame: .zero)
con
lazy var profileView: UIView = { return ProfileView(tableView: self.tableView) }()
Ahora nuestro
ProfileView
tiene un enlace a una vista de tabla, y podemos agregarlo como una subvista y establecer las restricciones correctas para ello.
Regrese a
ProfileView.swift
, agregue
addSubview(tableView)
al final de
addSubviews()
y establezca estas restricciones para
updateConstraints()
sobre
super.updateConstraints
:
tableView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top) tableView.autoPinEdge(.top, to: .bottom, of: segmentedControl, withOffset: 8)
La primera línea agrega tres restricciones entre la vista de tabla y su supervista: los lados derecho, izquierdo e inferior de la vista de tabla están unidos a los lados derecho, izquierdo e inferior de la vista de perfil.
La segunda línea une la parte superior de la vista de tabla a la parte inferior del control segmentado con un intervalo de ocho puntos entre ellas. Inicie la aplicación y el resultado debería verse así:
Genial, ahora todo está en su lugar, y podemos comenzar a introducir nuestras células.
UITableViewCell
Para implementar
UITableViewCell
, casi siempre tendremos que subclasificar esta clase, así que haga clic con el botón derecho en la carpeta
ContactCard
en el Navegador de proyectos, luego en "Nuevo archivo ...", luego en "Cocoa Touch Class" y "Siguiente".
Ingrese "UITableViewCell" en el campo "Subclase de:", y Xcode completará automáticamente el nombre de la clase "TableViewCell". Ingrese "ProfileView" antes de autocompletar para que el nombre final sea "ProfileInfoTableViewCell", luego haga clic en "Siguiente" y "Crear". Continúe y elimine los métodos creados, ya que no los necesitaremos. Si lo desea, primero puede leer sus descripciones para comprender por qué no las necesitamos en este momento.
Como dijimos anteriormente, nuestra celda contendrá información básica, que es el nombre del campo y su descripción, y por lo tanto necesitamos etiquetas para ellos.
lazy var titleLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "Title" return label }() lazy var descriptionLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "Description" label.textColor = .gray return label }()
Y ahora redefiniremos el inicializador para que la celda se pueda configurar:
override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(titleLabel) contentView.addSubview(descriptionLabel) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
En cuanto a las limitaciones, vamos a hacer un poco diferente, pero, sin embargo, muy útil:
override func updateConstraints() { let titleInsets = UIEdgeInsetsMake(16, 16, 0, 8) titleLabel.autoPinEdgesToSuperviewEdges(with: titleInsets, excludingEdge: .bottom) let descInsets = UIEdgeInsetsMake(0, 16, 4, 8) descriptionLabel.autoPinEdgesToSuperviewEdges(with: descInsets, excludingEdge: .top) descriptionLabel.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: 16) super.updateConstraints() }
Aquí comenzamos a usar
UIEdgeInsets
para establecer el espacio alrededor de cada etiqueta. Se puede crear un objeto
UIEdgeInsets
utilizando el
UIEdgeInsetsMake(top:, left:, bottom:, right:)
. Por ejemplo, para
titleLabel
decimos que queremos que el límite superior sea de cuatro puntos, y el derecho y el izquierdo a ocho. No nos importa la parte inferior, porque la excluimos, ya que la adjuntamos a la parte superior de la marca de descripción. Tómese un minuto para leer y visualizar todas las restricciones en su cabeza.
Ok, ahora podemos comenzar a dibujar celdas en nuestra vista de tabla. Pasemos a
ViewController.swift
y cambiemos la inicialización diferida de nuestra vista de tabla para registrar esta clase de celdas en la vista de tabla y establecer la altura de cada celda.
let profileInfoCellReuseIdentifier = "profileInfoCellReuseIdentifier" lazy var tableView: UITableView = { ... tableView.register(ProfileInfoTableViewCell.self, forCellReuseIdentifier: profileInfoCellReuseIdentifier) tableView.rowHeight = 68 return tableView }()
También agregamos una constante para el identificador de reutilización de celdas. Este identificador se utiliza para eliminar celdas de la vista de tabla cuando se muestran. Esta es una optimización que puede (y debe) usarse para ayudar a
UITableView
reutilizar celdas que se presentaron anteriormente para mostrar contenido nuevo en lugar de volver a dibujar la nueva celda desde cero.
Ahora déjame mostrarte cómo reutilizar celdas en una línea de código en el método
cellForRowAt
:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: profileInfoCellReuseIdentifier, for: indexPath) as! ProfileInfoTableViewCell return cell }
Aquí informamos a la vista de tabla del retiro de una celda reutilizable de la cola utilizando el identificador bajo el cual registramos la ruta a la celda en la que el usuario está a punto de aparecer. Luego,
ProfileInfoTableViewCell
la celda a
ProfileInfoTableViewCell
para poder acceder a sus propiedades y poder, por ejemplo, establecer el título y la descripción. Esto se puede hacer usando lo siguiente:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { ... switch indexPath.row { case 0: cell.titleLabel.text = "Phone Number" cell.descriptionLabel.text = "+234567890" case 1: cell.titleLabel.text = "Email" cell.descriptionLabel.text = "john@doe.co" case 2: cell.titleLabel.text = "LinkedIn" cell.descriptionLabel.text = "www.linkedin.com/john-doe" default: break } return cell }
Ahora configure
numberOfRowsInSection
para devolver "3" e iniciar su aplicación.
Increíble verdad?
Células de tamaño propio
Quizás, y lo más probable, habrá un caso en el que desee que diferentes celdas tengan una altura de acuerdo con la información dentro de ellas, que no se conoce de antemano. Para hacer esto, necesita una vista de tabla con dimensiones calculadas automáticamente, y de hecho hay una manera muy simple de hacerlo.
En primer lugar, en
ProfileInfoTableViewCell
agregue esta línea a la
descriptionLabel
pereza del inicializador
ProfileInfoTableViewCell
:
label.numberOfLines = 0
Regrese al
ViewController
y agregue estas dos líneas al inicializador de la vista de tabla:
lazy var tableView: UITableView = { ... tableView.estimatedRowHeight = 64 tableView.rowHeight = UITableViewAutomaticDimension return tableView }()
Aquí informamos a la vista de tabla que la altura de la fila debe tener un valor calculado automáticamente en función de su contenido.
En cuanto a la altura estimada de la fila:
"Proporcionar una estimación no negativa de la altura de las filas puede mejorar el rendimiento de cargar la vista de tabla". - Documentos de Apple
En
ViewDidLoad
necesitamos volver a cargar la vista de tabla para que estos cambios surtan efecto:
override func viewDidLoad() { super.viewDidLoad() ... DispatchQueue.main.async { self.tableView.reloadData() } }
Ahora continúe y agregue otra celda, aumentando el número de filas a cuatro y agregando otra
switch
a
cellForRow
:
case 3: cell.titleLabel.text = "Address" cell.descriptionLabel.text = "45, Walt Disney St.\n37485, Mickey Mouse State"
Ahora ejecute la aplicación, y debería verse así:
Conclusión
Increíble verdad? Y como recordatorio de por qué realmente codificamos nuestra interfaz de usuario, aquí hay una publicación de blog completa escrita por nuestro equipo móvil sobre por qué no estamos
usando guiones gráficos en Instabug .
Lo que hiciste en dos partes de esta lección:
- Se eliminó el archivo
main.storyboard
de su proyecto. - Creamos
UIWindow
programación y le asignamos un rootViewController
. - Creó varios elementos de la interfaz de usuario en el código, como etiquetas, vistas de imágenes, controles segmentados y vistas de tablas con sus celdas.
UINavigationBar
anidado en su aplicación.- Creó un tamaño dinámico
UITableViewCell
.