El diseño atómico y el diseño del sistema son populares en el diseño: esto es cuando todo consiste en componentes, desde controles hasta pantallas. No es difícil para un programador escribir controles separados, pero ¿qué hacer con pantallas enteras?
Echemos un vistazo al ejemplo de Año Nuevo:
- peguemos todo junto;
- dividido en controladores: seleccione navegación, plantilla y contenido;
- reutilizar código para otras pantallas.

Todo en un montón
La pantalla de este año nuevo habla sobre los horarios especiales de apertura de las pizzerías. Es bastante simple, por lo que no será un delito convertirlo en un controlador:

Pero La próxima vez, cuando necesitemos una pantalla similar, tendremos que repetirla nuevamente y luego hacer los mismos cambios en todas las pantallas. Bueno, no sucede sin ediciones.
Por lo tanto, es más razonable dividirlo en partes y usarlo para otras pantallas. Destaqué tres:
- navegación
- una plantilla con un área para contenido y un lugar para acciones en la parte inferior de la pantalla,
- Contenido único en el centro.
Seleccione cada parte en su propio UIViewController
.
Navegación de contenedores
Los ejemplos más llamativos de contenedores de navegación son UINavigationController
y UITabBarController
. Cada uno ocupa una franja en la pantalla bajo sus propios controles, y deja el espacio restante para otro UIViewController
.
En nuestro caso, habrá un contenedor para todas las pantallas modales con un solo botón de cierre.
Cual es el punto?Si queremos mover el botón hacia la derecha, solo necesitaremos cambiarlo en un controlador.
O, si decidimos mostrar todas las ventanas modales con una animación especial, y cerrar de forma interactiva con un deslizamiento, como en las tarjetas de historia de AppStore. Entonces, UIViewControllerTransitioningDelegate
deberá configurarse solo para este controlador.

Puede usar una container view
para separar los controladores: creará una UIView
en el padre e insertará la UIView
controlador hijo en él.

Estire la container view
hasta el borde de la pantalla. Safe area
se aplicará automáticamente al controlador secundario:

Patrón de pantalla
El contenido es obvio en la pantalla: imagen, título, texto. El botón parece ser parte de él, pero el contenido es dinámico en diferentes iPhones, y el botón es fijo. Son visibles dos sistemas con diferentes tareas: uno muestra el contenido y el otro lo integra y lo alinea. Deben dividirse en dos controladores.

El primero es responsable del diseño de la pantalla: el contenido debe estar centrado y el botón clavado en la parte inferior de la pantalla. El segundo dibujará el contenido.

Sin una plantilla, todos los controladores son similares, pero los elementos bailan.
Los botones en la última pantalla son diferentes, depende del contenido. La delegación ayudará a resolver el problema: la plantilla del controlador solicitará controles del contenido y los mostrará en su UIStackView
.
Los botones se pueden conectar al controlador a través de objetos relacionados. Su IBOutlet
e IBAction
se almacenan en el controlador de contenido, solo los elementos no se agregan a la jerarquía.

Puede obtener elementos del contenido y agregarlos a la plantilla en la etapa de preparación de UIStoryboardSegue
:
En el setter, agregamos controles a UIStackView
:
Como resultado, nuestro controlador se dividió en tres partes: navegación, plantilla y contenido. En la imagen, toda la container view
muestra en gris:

Tamaño del controlador dinámico
El controlador de contenido tiene su propio tamaño máximo, está limitado por constraints
internas.
Container view
agrega constores basados en la Autoresizing mask
y entran en conflicto con las dimensiones internas del contenido. El problema se resuelve en el código: en el controlador de contenido, debe indicar que no está afectado por los constores de la Autoresizing mask
:

Hay dos pasos más para Interface Builder:
Paso 1. Especifique el Intrinsic size
para la vista UIView
. Los valores reales aparecerán después del lanzamiento, pero por ahora pondremos los adecuados.

Paso 2. Para el controlador de contenido, especifique Simulated Size
. Puede que no coincida con el tamaño pasado.
Hubo errores de diseño, ¿qué debo hacer?Se producen errores cuando AutoLayout
no puede descubrir cómo descomponer los elementos en el tamaño actual.
Muy a menudo, el problema desaparece después de cambiar las prioridades de la constante. UIView
dejarlos para que uno de los UIView
pueda expandirse / contraerse más que los demás.
Nos dividimos en partes y escribimos código
Dividimos el controlador en varias partes, pero hasta ahora no podemos reutilizarlos, la interfaz de UIStoryboard
difícil de extraer en partes. Si necesitamos transferir algunos datos al contenido, tendremos que acceder a ellos a través de toda la jerarquía. Debería ser al revés: primero tome el contenido, configúrelo y luego envuélvalo en los contenedores necesarios. Como una bombilla
Tres tareas aparecen en nuestro camino:
- Separe cada controlador en su propio
UIStoryboard
. - Rechace la
container view
, agregue controladores a los contenedores en el código. - Ata todo de nuevo.
Compartir UIStoryboard
UIStoryboard
crear dos UIStoryboard
adicionales y copiar y pegar el controlador de navegación y el controlador de plantilla en ellos. Embed segue
se interrumpirá, pero la container view
del container view
con restricciones configuradas se transferirá. Las restricciones deben guardarse y la container view
del container view
debe reemplazarse con una vista de UIView
normal.
La forma más fácil es cambiar el tipo de vista de Contenedor en el código de UIStoryboard.- abra
UIStoryboard
como un código (menú contextual del archivo → Abrir como ... → Código fuente); cambie el tipo de containerView
para view
. Es necesario cambiar las etiquetas de apertura y cierre .
Del mismo modo, puede cambiar, por ejemplo, UIView
a UIScrollView
, si es necesario. Y viceversa.

Configuramos el controlador a la propiedad de is initial view controller
, y llamaremos al UIStoryboard
como controlador.
Cargamos el controlador desde UIStoryboard.Si el nombre del controlador coincide con el nombre de UIStoryboard
, la descarga se puede envolver en un método que encontrará el archivo deseado:
protocol Storyboardable { } extension Storyboardable where Self: UIViewController { static func instantiateInitialFromStoryboard() -> Self { let controller = storyboard().instantiateInitialViewController() return controller! as! Self } static func storyboard(fileName: String? = nil) -> UIStoryboard { let storyboard = UIStoryboard(name: fileName ?? storyboardIdentifier, bundle: nil) return storyboard } static var storyboardIdentifier: String { return String(describing: self) } static var storyboardName: String { return storyboardIdentifier } }
Si el controlador se describe en .xib
, el constructor estándar se cargará sin tales bailes. Por desgracia, .xib
puede contener solo un controlador, a menudo esto no es suficiente: en un buen caso, una pantalla consta de varias. Por lo tanto, usamos UIStoryborad
, es fácil dividir la pantalla en partes.
Agregar un controlador en el código
Para que el controlador funcione correctamente, necesitamos todos los métodos de su ciclo de vida: will/did-appear/disappear
.
Para la visualización correcta, debe llamar a 5 pasos:
willMove(toParent parent: UIViewController?) addChild(_ childController: UIViewController) addSubview(_ subivew: UIView) layout didMove(toParent parent: UIViewController?)
Apple sugiere reducir el código a 4 pasos, porque addChild()
llamará willMove(toParent)
. En resumen:
addChild(_ childController: UIViewController) addSubview(_ subivew: UIView) layout didMove(toParent parent: UIViewController?)
Para simplificar, puede envolverlo todo en extension
. Para nuestro caso, necesitamos una versión con insertSubview()
.
extension UIViewController { func insertFullframeChildController(_ childController: UIViewController, toView: UIView? = nil, index: Int) { let containerView: UIView = toView ?? view addChild(childController) containerView.insertSubview(childController.view, at: index) containerView.pinToBounds(childController.view) childController.didMove(toParent: self) } }
Para eliminar, necesita los mismos pasos, solo que en lugar del controlador principal, debe establecer nil
. Ahora removeFromParent()
llama a didMove(toParent: nil)
, y el diseño no es necesario. La versión abreviada es muy diferente:
willMove(toParent: nil) view.removeFromSuperview() removeFromParent()
Diseño
Establecer restricciones
Para establecer correctamente el tamaño del controlador, usaremos AutoLayout
. Necesitamos clavar todos los lados a todos los lados:
extension UIView { func pinToBounds(_ view: UIView) { view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ view.topAnchor.constraint(equalTo: topAnchor), view.bottomAnchor.constraint(equalTo: bottomAnchor), view.leadingAnchor.constraint(equalTo: leadingAnchor), view.trailingAnchor.constraint(equalTo: trailingAnchor) ]) } }
Agregar un controlador secundario en el código
Ahora todo se puede combinar:
Debido a la frecuencia de uso, podemos envolver todo esto en extension
:
También se necesita un método similar para el controlador de plantilla. prepare(for segue:)
solía configurarse en prepare(for segue:)
, pero ahora puede vincularlo en el método de prepare(for segue:)
del controlador:
Crear un controlador se ve así:
Conectar una nueva pantalla a la plantilla es simple:
- eliminar lo que no es relevante para el contenido;
- especificar botones de acción implementando el protocolo OnboardingViewControllerDatasource;
- escriba un método que vincule una plantilla y contenido.
Más sobre contenedores
Barra de estado
A menudo es necesario que la visibilidad de la status bar
sea controlada por un controlador con contenido, no un contenedor. Hay un par de property
para esto:
Con estas property
puede crear una cadena de controladores, este último será responsable de mostrar la status bar
.
Zona segura
Si los botones del contenedor se superponen al contenido, entonces debe aumentar la zona safeArea
. Esto se puede hacer en código: establezca additinalSafeAreaInsets
para controladores secundarios. Puedes llamarlo desde embedController()
:
private func addSafeArea(to controller: UIViewController) { if #available(iOS 11.0, *) { let buttonHeight = CGFloat(30) let topInset = UIEdgeInsets(top: buttonHeight, left: 0, bottom: 0, right: 0) controller.additionalSafeAreaInsets = topInset } }
Si agrega 30 puntos en la parte superior, el botón dejará de superponer contenido y safeArea
ocupará el área verde:

Márgenes Preservar los márgenes de supervisión
Los controladores tienen margins
estándar. Por lo general, son iguales a 16 puntos de cada lado de la pantalla y solo en tamaños Plus son 20 puntos.
Según los margins
puede crear constantes, la sangría en el borde será diferente para diferentes iPhones:

Cuando ponemos una UIView
en otra, los margins
se reducen a la mitad: a 8 puntos. Para evitar esto, debe incluir Preserve superview margins
. Luego, los margins
UIView
secundario serán iguales a los margins
padre. Es adecuado para contenedores de pantalla completa.
El final
Los controladores de contenedores son una herramienta poderosa. Simplifican el código, separan las tareas y pueden reutilizarse. Puede escribir controladores anidados de cualquier manera: en UIStoryboard
, en .xib
o simplemente en código. Lo más importante es que son fáciles de crear y divertidos de usar.
→ Un ejemplo de un artículo sobre GitHub
¿Tienes pantallas desde las cuales valdría la pena hacer una plantilla? ¡Comparte en los comentarios!