Creación de elementos de interfaz mediante programación mediante PureLayout (Parte 2)

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.

imagen

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í:

imagen

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í:

imagen

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":

imagen

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:

 /* // Only override draw() if you perform custom drawing. // An empty implementation adversely affects performance during animation. override func draw(_ rect: CGRect) { // Drawing code } */ 

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.

imagen

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

imagen

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

imagen

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() { // Insert code here super.updateConstraints() // Always at the bottom of the function } 

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:

imagen

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:

  1. public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int - este método le dice a la vista de tabla cuántas filas queremos mostrar.
  2. 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í:

imagen

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.

imagen

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í:

imagen

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 .

Source: https://habr.com/ru/post/447374/


All Articles