
Apple creó Storyboards para que los desarrolladores puedan visualizar las pantallas de las aplicaciones de iOS y las relaciones entre ellas. No a todos les gustó esta herramienta, y por una buena razón. He conocido muchos artículos que critican Storyboards, pero no he encontrado un análisis detallado e imparcial de todos los pros y los contras, teniendo en cuenta las mejores prácticas. Al final, decidí escribir un artículo así.
Intentaré analizar en detalle las desventajas y ventajas de usar Storyboards. Después de sopesarlos, puede tomar una decisión significativa si son necesarios en el proyecto o no. Esta decisión no tiene que ser radical. Si en algunas situaciones los Storyboards crean problemas, en otras su uso está justificado: ayuda a resolver tareas de manera efectiva y a escribir código simple y fácil de mantener.
Comencemos con las deficiencias y analicemos si todas ellas siguen siendo relevantes.
Desventajas
1. Los guiones gráficos tienen dificultades para gestionar conflictos al fusionar cambios
Storyboard es un archivo XML. Es menos legible que el código, por lo que resolver conflictos es más difícil. Pero esta complejidad también depende de cómo trabajemos con el Storyboard. Puede simplificar enormemente su tarea si sigue las siguientes reglas:
- No coloque la interfaz de usuario completa en un solo guión gráfico, divídalo en varios más pequeños. Esto permitirá distribuir el trabajo en Storyboards entre los desarrolladores sin riesgo de conflictos, y en caso de que sea inevitable, simplificará la tarea de resolverlos.
- Si necesita usar la misma Vista en varios lugares, selecciónela en una subclase separada con su propio archivo Xib.
- Realice confirmaciones con más frecuencia, ya que es mucho más fácil trabajar con los cambios que vienen en pequeños pedazos.
El uso de varios guiones gráficos en lugar de uno nos imposibilita ver el mapa completo de la aplicación en un solo archivo. Pero a menudo esto no es necesario, solo la parte específica en la que estamos trabajando en este momento es suficiente.
2. Los guiones gráficos evitan la reutilización del código
Si estamos hablando de usar solo Storyboards sin Xibs en el proyecto, entonces seguramente surgirán problemas. Sin embargo, los Xibs, en mi opinión, son elementos necesarios cuando se trabaja con Storyboards. Gracias a ellos, puede crear fácilmente Vistas reutilizables, que también son convenientes para trabajar en código.
Primero, cree la clase base
XibView
, que es responsable de representar la
UIView
creada en Xib en el Storyboard:
@IBDesignable class XibView: UIView { var contentView: UIView? }
XibView
cargará la
UIView
de Xib en
contentView
y la agregará como su subvista. Hacemos esto en el método
setup()
:
private func setup() { guard let view = loadViewFromNib() else { return } view.frame = bounds view.autoresizingMask = [.flexibleWidth, .flexibleHeight] addSubview(view) contentView = view }
El método
loadViewFromNib()
se ve así:
private func loadViewFromNib() -> UIView? { let nibName = String(describing: type(of: self)) let nib = UINib(nibName: nibName, bundle: Bundle(for: XibView.self)) return nib.instantiate(withOwner: self, options: nil).first as? UIView }
El método
setup()
debería llamarse en inicializadores:
override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setup() }
La clase
XibView
lista. Las vistas reutilizadas, cuya apariencia se representa en un archivo Xib, se heredarán de
XibView
:
final class RedView: XibView { }

Si ahora agrega una nueva
UIView
al Storyboard y establece su clase en
RedView
, entonces todo se mostrará con éxito:

La creación de una instancia de
RedView
en código ocurre de la manera habitual:
let redView = RedView()
Otro detalle útil que no todos pueden conocer es la capacidad de agregar colores al directorio
.xcassets . Esto le permite cambiarlos globalmente en todos los Storyboards y Xibs donde se usan.
Para agregar color, haga clic en "+" en la parte inferior izquierda y seleccione "Nuevo conjunto de colores":

Especifique el nombre y el color deseados:

El color creado aparecerá en la sección "Colores con nombre":

Además, se puede obtener en el código:
innerView.backgroundColor = UIColor(named: "BackgroundColor")
3. No puede usar inicializadores personalizados para UIViewControllers
creados en Storyboard
En el caso del Storyboard, no podemos pasar dependencias en los inicializadores de los
UIViewControllers
. Por lo general, se ve así:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { guard segue.identifier == "detail", let detailVC = segue.destination as? DetailViewController else { return } let object = Object() detailVC.object = object }
Este código se puede hacer mejor usando algún tipo de constante para representar identificadores o herramientas como
SwiftGen y
R.swift , o tal vez incluso
Realizar . Pero de esta manera solo nos deshacemos de los literales de cadena y agregamos azúcar sintáctico, y no resolvemos los problemas que surgen:
- ¿Cómo sé cómo
DetailViewController
configura DetailViewController
en el ejemplo anterior? Si es nuevo en el proyecto y no tiene este conocimiento, deberá abrir un archivo con una descripción de este controlador y estudiarlo.
- Las propiedades
DetailViewController
establecen después de la inicialización, lo que significa que deben ser opcionales. Es necesario manejar casos cuando alguna propiedad es nil
; de lo contrario, la aplicación puede bloquearse en el momento más inoportuno. Puede marcar propiedades como opcionales expandidas implícitamente ( var object: Object!
), Pero la esencia no cambiará.
- Las propiedades deben estar marcadas como
var
, no let
. Entonces, una situación es posible cuando alguien de afuera quiere cambiarlos. DetailViewController
debería manejar tales situaciones.
Una solución se describe en
este artículo .
4. A medida que el Storyboard crece, la navegación en él se vuelve más difícil
Como notamos anteriormente, no es necesario poner todo en un Storyboard, es mejor dividirlo en varios más pequeños. Con el advenimiento de
Storyboard Reference, se ha vuelto muy simple.
Agregue la referencia del guión gráfico de la biblioteca de objetos al guión gráfico:

Establecemos los valores de campo requeridos en el
Inspector de atributos : este es el nombre del archivo de Storyboard y, si es necesario, la
ID de referencia , que corresponde a la
ID de Storyboard de la pantalla deseada. Por defecto, el
Controlador de vista inicial cargará:

Si especifica un nombre no válido en el campo Storyboard o hace referencia a una ID de Storyboard inexistente, Xcode le advertirá sobre esto en la etapa de compilación.
5. Xcode se ralentiza al cargar guiones gráficos
Si el Storyboard contiene una gran cantidad de pantallas con numerosas restricciones, cargarlo realmente llevará algún tiempo. Pero, de nuevo, es mejor dividir el Storyboard grande en otros más pequeños. Por separado, se cargan mucho más rápido y se vuelve más conveniente trabajar con ellos.
6. Los guiones gráficos son frágiles, un error puede hacer que la aplicación se bloquee en tiempo de ejecución
Los principales puntos débiles:
- Errores en los
UICollectionViewCell
UITableViewCell
y UICollectionViewCell
.
- Errores en segues identificadores.
- Usando una subclase de
UIView
que ya no existe.
- Sincronización de
IBActions
e IBOutlets
con código.
Todo esto y algunos otros problemas pueden provocar el bloqueo de la aplicación en tiempo de ejecución, lo que significa que es probable que tales errores caigan en la versión de lanzamiento. Por ejemplo, cuando establecemos identificadores de celda o segues en el Storyboard, deben copiarse en el código donde sea que se usen. Al cambiar el identificador en un lugar, se debe cambiar en el resto. Existe la posibilidad de que simplemente lo olvide o haga un error tipográfico, pero solo aprenda sobre el error mientras la aplicación se está ejecutando.
Puede reducir la probabilidad de errores al deshacerse de los literales de cadena en su código. Para esto, a los
UICollectionViewCell
UITableViewCell
y
UICollectionViewCell
se les pueden asignar los nombres de las clases de celdas: por ejemplo, el identificador
ItemTableViewCell
será la cadena "ItemTableViewCell". En el código, obtenemos la celda así:
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ItemTableViewCell.self)) as! ItemTableViewCell
Puede agregar la función genérica correspondiente a
UITableView
:
extension UITableView { open func dequeueReusableCell<T>() -> T where T: UITableViewCell { return dequeueReusableCell(withIdentifier: String(describing: T.self)) as! T } }
Y luego se hace más fácil obtener la célula:
let cell: ItemTableViewCell = tableView.dequeueReusableCell()
Si de repente olvida especificar el valor del identificador de celda en el Guión gráfico, Xcode mostrará una advertencia, por lo que no debe ignorarlos.
En cuanto a los identificadores de segues, puede usar enumeraciones para ellos. Creemos un protocolo especial:
protocol SegueHandler { associatedtype SegueIdentifier: RawRepresentable }
UIViewController
que admita este protocolo deberá definir un tipo anidado con el mismo nombre. Enumera todos los identificadores de
UIViewController
que este
UIViewController
puede procesar:
extension StartViewController: SegueHandler { enum SegueIdentifier: String { case signIn, signUp } }
Además, en la extensión de protocolo
SegueHandler
, definimos dos funciones: una acepta un
UIStoryboardSegue
y devuelve el valor
SegueIdentifier
correspondiente, y el otro simplemente llama a
performSegue
, tomando la entrada
SegueIdentifier
:
extension SegueHandler where Self: UIViewController, SegueIdentifier.RawValue == String { func performSegue(withIdentifier segueIdentifier: SegueIdentifier, sender: AnyObject?) { performSegue(withIdentifier: segueIdentifier.rawValue, sender: sender) } func segueIdentifier(for segue: UIStoryboardSegue) -> SegueIdentifier { guard let identifier = segue.identifier, let identifierCase = SegueIdentifier(rawValue: identifier) else { fatalError("Invalid segue identifier \(String(describing: segue.identifier)).") } return identifierCase } }
Y ahora en un
UIViewController
que admite el nuevo protocolo, puede trabajar con
prepare(for:sender:)
siguiente manera:
extension StartViewController: SegueHandler { enum SegueIdentifier: String { case signIn, signUp } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { switch segueIdentifier(for: segue) { case .signIn: print("signIn") case .signUp: print("signUp") } } }
Y ejecuta segue así:
performSegue(withIdentifier: .signIn, sender: nil)
Si agrega un nuevo identificador al
SegueIdentifier
, entonces Xcode ciertamente lo obligará a procesar en
switch/case
.
Otra opción para deshacerse de los literales de cadena como identificadores, segues y otros es utilizar herramientas de generación de código como
R.swift .
7. Los guiones gráficos son menos flexibles que el código.
Si, esto es verdad. Si la tarea es crear una pantalla compleja con animaciones y efectos que Storyboard no pueda manejar, ¡entonces debe usar el código!
8. Los UIViewControllers
no permiten cambiar el tipo de UIViewControllers
especiales
Por ejemplo, cuando necesita cambiar el tipo de
UITableViewController
a
UICollectionViewController
, debe eliminar el objeto, agregar uno nuevo con otro tipo y
UITableViewController
a configurarlo. Aunque este no es un caso frecuente, vale la pena señalar que dichos cambios se realizan más rápido en el código.
9. Los guiones gráficos agregan dos dependencias adicionales al proyecto. Pueden contener errores que el desarrollador no puede corregir.
Este es Interface Builder y el analizador de Storyboards. Tales casos son raros y a menudo pueden ser evitados por otras soluciones.
10. Revisión sofisticada del código
Tenga en cuenta que la revisión de código no es realmente una búsqueda de errores. Sí, se encuentran en el proceso de visualización del código, pero el objetivo principal es identificar las debilidades que pueden crear problemas a largo plazo. Para Storyboards, este es principalmente el trabajo de
Auto Layout . No debe haber ninguna
ambigüedad y
fuera de lugar . Para encontrarlos, simplemente use la búsqueda en el XML de Storyboard para las líneas “ambiguous =“ YES ”” y “misplaced =“ YES ”” o simplemente abra Storyboard en Interface Builder y busque puntos rojos y amarillos:

Sin embargo, esto puede no ser suficiente. Los conflictos entre restricciones también se pueden detectar mientras se ejecuta la aplicación. Si ocurre una situación similar, se muestra información sobre esto en la consola. Tales casos no son infrecuentes, por lo tanto, su búsqueda también debe tomarse en serio.
Todo lo demás, hacer coincidir la posición y el tamaño de los elementos con el diseño, la unión correcta de
IBOutlets
e
IBActions
, no es para revisión de código.
Además, es importante realizar confirmaciones con más frecuencia, entonces será más fácil para el revisor ver los cambios en partes pequeñas. Podrá profundizar en los detalles sin perderse nada. Esto, a su vez, tendrá un efecto positivo en la calidad de la revisión del código.
Resumen
En la lista de defectos de Storyboards, dejé 4 elementos (en orden descendente de su valor):
- Los guiones gráficos tienen dificultades para gestionar conflictos al fusionar cambios.
- Los guiones gráficos son menos flexibles que el código.
- Los guiones gráficos son frágiles, un error puede provocar un bloqueo en tiempo de ejecución.
- No puede usar inicializadores personalizados para
UIViewControllers
creados en Storyboard.
Los beneficios
1. Visualización de la interfaz de usuario y restricciones.
Incluso si es un principiante y acaba de comenzar un proyecto desconocido, puede encontrar fácilmente el punto de entrada a la aplicación y cómo llegar a la pantalla deseada desde ella. Usted sabe cómo se verá cada botón, etiqueta o campo de texto, qué posición tomarán, cómo los afectan las restricciones, cómo interactúan con otros elementos. Con unos pocos clics, puede crear fácilmente una nueva
UIView
, personalizar su apariencia y comportamiento. El diseño automático nos permite trabajar con
UIView
forma natural, como si dijéramos: "Este botón debe estar a la izquierda de esa etiqueta y tener la misma altura". Esta experiencia de interfaz de usuario es intuitiva y efectiva. Puede intentar dar ejemplos en los que el código bien escrito ahorre más tiempo al crear algunos elementos de la interfaz de usuario, pero a nivel mundial esto no cambia mucho. Storyboard hace bien su trabajo.
Por separado, tenga en cuenta el diseño automático. Esta es una herramienta muy poderosa y útil, sin la cual sería difícil crear una aplicación que soporte todos los diferentes tamaños de pantalla. Interface Builder le permite ver el resultado de trabajar con Auto Layout sin iniciar la aplicación, y si algunas restricciones no se ajustan al esquema general, Xcode le advertirá de inmediato. Por supuesto, hay casos en que Interface Builder no puede proporcionar el comportamiento necesario de una interfaz muy dinámica y compleja, por lo que debe confiar en el código. Pero incluso en tales situaciones, puede hacerlo en Interface Builder y complementarlo con solo un par de líneas de código.
Veamos algunos ejemplos que demuestran las características útiles de Interface Builder.
Tablas dinámicas basadas en UIStackView
Cree un nuevo
UIViewController
, agregue una pantalla completa
UIScrollView
:

En
UIScrollView
agregue un
UIStackView
vertical,
UIStackView
a los bordes y establezca la altura y el ancho igual a
UIScrollView
. A esta altura, asigne
prioridad = Baja (250) :

A continuación, cree todas las celdas necesarias y agréguelas a
UIStackView
. Tal vez sea
UIView
ordinario en una sola copia, o tal vez
UIView
, para lo cual creamos nuestro propio archivo Xib. En cualquier caso, toda la interfaz de usuario de esta pantalla está en el Guión gráfico y, gracias al diseño automático configurado correctamente, el desplazamiento funcionará perfectamente, adaptándose al contenido:

También podemos hacer que las células se adapten al tamaño de su contenido. Agregue
UILabel
a cada celda,
UILabel
a los bordes:

Ya está claro cómo se verá todo esto en el tiempo de ejecución. Puede adjuntar cualquier acción a las celdas, por ejemplo, cambiar a otra pantalla. Y todo esto sin una sola línea de código.
Además, si configura
hidden = true
para una
UIView
desde un
UIStackView
, no solo se ocultará, sino que tampoco ocupará espacio.
UIStackView
recalculará automáticamente sus tamaños:

Células auto dimensionantes
En el
inspector de tamaño de la tabla, establezca
Altura de fila = Automática y
Estimación en algún valor promedio:

Para que esto funcione, las restricciones deben configurarse correctamente en las celdas mismas y permitir un cálculo preciso de la altura de la celda en función del contenido en tiempo de ejecución. Si no está claro qué está en juego, hay una muy buena explicación en la
documentación oficial .
Como resultado, al iniciar la aplicación, veremos que todo se muestra correctamente:

Mesa de auto-dimensionamiento
Necesita implementar este comportamiento de tabla:

¿Cómo lograr un cambio dinámico similar en altura? A diferencia de
UILabel
,
UIButton
y otras subclases de
UIView
, es un poco más difícil de hacer con una tabla, ya que el
Tamaño del contenido intrínseco no depende del tamaño de las celdas que contiene. Ella no puede calcular su altura en función del contenido, pero existe la oportunidad de ayudarla con esto.
Tenga en cuenta que en algún momento del video la altura de la tabla deja de cambiar, alcanzando un cierto valor máximo. Esto se puede lograr estableciendo la
restricción de altura de la tabla con el valor
Relación = Menor o igual :

En esta etapa, Interface Builder aún no sabe qué altura tendrá la tabla, solo conoce su valor máximo igual a 200 (de la restricción de altura). Como se señaló anteriormente, el tamaño del contenido intrínseco no es igual al contenido de la tabla. Sin embargo, tenemos la capacidad de establecer el marcador de posición en el campo
Tamaño intrínseco :

Este valor es válido solo mientras se trabaja con Interface Builder. Por supuesto, el tamaño del contenido intrínseco no tiene que ser igual a este valor en tiempo de ejecución. Acabamos de decirle a Interface Builder que todo está bajo control.
A continuación, cree una nueva subclase de la tabla
CustomTableView
:
final class CustomTableView: UITableView { override var contentSize: CGSize { didSet { invalidateIntrinsicContentSize() } } override var intrinsicContentSize: CGSize { return contentSize } }
Uno de esos casos cuando el código es necesario. Aquí llamamos
invalidateIntrinsicContentSize
cada vez que cambia el
contentSize
la tabla. Esto permitirá que el sistema acepte el nuevo tamaño de contenido intrínseco. A su vez, devuelve
contentSize
, obligando a la tabla a ajustar dinámicamente su altura y mostrar un cierto número de celdas sin desplazarse. El desplazamiento aparece en el momento en que alcanzamos el límite de restricción de altura.
Las tres características de Interface Builder se pueden combinar entre sí. Añaden más flexibilidad a las opciones de organización de contenido sin la necesidad de restricciones adicionales o cualquier
UIView
.
2. La capacidad de ver instantáneamente el resultado de sus acciones.
Si
UIView
el tamaño de
UIView
, lo movió un par de puntos a un lado o cambió el color de fondo, verá inmediatamente cómo se verá en tiempo de ejecución sin tener que iniciar la aplicación. No es necesario preguntarse por qué algunos botones no aparecieron en la pantalla o por qué el comportamiento de
UIView
no
UIView
deseado.
El uso de
@IBInspectable
revela este beneficio aún más interesante. Agregue dos
UILabel
y dos propiedades a
RedView
:
final class RedView: XibView { @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var subtitleLabel: UILabel! @IBInspectable var title: String = "" { didSet { titleLabel.text = title } } @IBInspectable var subtitle: String = "" { didSet { subtitleLabel.text = subtitle } } }
Aparecerán dos nuevos campos en el
Inspector de atributos para
RedView
:
Title
y
Subtitle
, que marcamos como
@IBInspectable
:

Si intentamos ingresar valores en estos campos, veremos de inmediato cómo se verá todo en el tiempo de ejecución:

Puede controlar cualquier cosa:
cornerRadius
,
borderWidth
,
borderColor
. Por ejemplo, ampliamos la clase base
UIView
:
extension UIView { @IBInspectable var cornerRadius: CGFloat { set { layer.cornerRadius = newValue } get { return layer.cornerRadius } } @IBInspectable var borderWidth: CGFloat { set { layer.borderWidth = newValue } get { return layer.borderWidth } } @IBInspectable var borderColor: UIColor? { set { layer.borderColor = newValue?.cgColor } get { return layer.borderColor != nil ? UIColor(cgColor: layer.borderColor!) : nil } } @IBInspectable var rotate: CGFloat { set { transform = CGAffineTransform(rotationAngle: newValue * .pi/180) } get { return 0 } } }
Vemos que el Inspector de atributos del objeto
RedView
adquirido 4 campos nuevos más, con los que ahora también puede jugar:

3. Vista previa de todos los tamaños de pantalla a la vez
Así que arrojamos los elementos necesarios en la pantalla, ajustamos su apariencia y agregamos las restricciones necesarias. ¿Cómo descubrimos si el contenido se mostrará correctamente en diferentes tamaños de pantalla? Por supuesto, puede ejecutar la aplicación en cada simulador, pero tomará mucho tiempo. Hay una mejor opción: Xcode tiene un modo de vista previa, le permite ver varios tamaños de pantalla a la vez sin iniciar la aplicación.
Llamamos al
editor Asistente , en él haga clic en el primer segmento de la barra de transición, seleccione
Vista previa -> Configuración. Tablero de historia (como ejemplo):

Al principio solo vemos una pantalla, pero podemos agregar tanto como sea necesario haciendo clic en "+" en la esquina inferior izquierda y seleccionando los dispositivos necesarios de la lista:

Además, si Storyboard admite varios idiomas, puede ver cómo se verá la pantalla seleccionada con cada uno de ellos:

El idioma se puede seleccionar para todas las pantallas a la vez y para cada una individualmente.
4. Eliminar el código de la interfaz de usuario de la plantilla
La creación de una interfaz de usuario sin Interface Builder se acompaña de una gran cantidad de código repetitivo o de superclases y extensiones que implican un trabajo de mantenimiento adicional. Este código puede infiltrarse en otras partes de la aplicación, lo que dificulta la lectura y la búsqueda. Usar Storyboards y Xibs puede descargar código, haciéndolo más enfocado en la lógica.
5. Clases de tamaño
Cada año, aparecen nuevos dispositivos, para los cuales debe adaptar la interfaz de usuario. El concepto de
variaciones de
rasgos y, en particular, las
clases de tamaño , que le permiten crear una interfaz de usuario para cualquier tamaño y orientación de la pantalla, ayuda en esto.
Las clases de tamaño clasifican la altura (h) y el ancho (w) de las pantallas del dispositivo en términos de
compacto y
regular (
C y
R ). Por ejemplo, el iPhone 8 tiene una clase de tamaño
(wC hR) en orientación vertical y
(wC hC) en horizontal, y el iPhone 8 Plus tiene
(wC hR) y
(wR hC), respectivamente. El resto de los dispositivos se pueden encontrar
aquí .
En un Storyboard o Xib para cada una de las clases de tamaño, puede almacenar su propio conjunto de datos, y la aplicación usará el apropiado según el dispositivo y la orientación de la pantalla en tiempo de ejecución, identificando así la clase de tamaño actual.
Si algunos parámetros de diseño son los mismos para todas las clases de tamaño, se pueden configurar en la categoría " Cualquiera ", que ya está seleccionada de forma predeterminada.Por ejemplo, configure el tamaño de fuente según la clase de tamaño. Seleccionamos el dispositivo iPhone 8 Plus para verlo en Storyboard en orientación vertical y agregamos una nueva condición para font
: si el ancho es Regular (establece todo lo demás en "Cualquiera"), entonces el tamaño de la fuente debe ser 37:
Ahora, si cambiamos la orientación de la pantalla, el tamaño de la fuente aumentar: una nueva condición funcionará, ya que en orientación horizontal, el iPhone 8 Plus tiene clase de tamaño (wR hC) . En el Storyboard, dependiendo de la clase de tamaño, también puede ocultar Vistas, habilitar / deshabilitar restricciones, cambiar su valorconstant
y mucho mas Lea más sobre cómo hacer todo esto aquí .En la captura de pantalla anterior, vale la pena señalar el panel inferior con la elección del dispositivo para mostrar el diseño. Le permite verificar rápidamente la adaptabilidad de la interfaz de usuario en cualquier dispositivo y para cualquier orientación de pantalla, y también muestra la clase de tamaño de la configuración actual (junto al nombre del dispositivo). Entre otras cosas, hay un botón Variar para los rasgos a la derecha . Su propósito es permitir variaciones de rasgos solo para una categoría específica de ancho, alto o ancho y alto al mismo tiempo. Por ejemplo, al seleccionar un iPad con una clase de tamaño (wR hR) , haga clic en "Variar para los rasgos" y marque la casilla junto a ancho y alto. Ahora, todos los cambios de diseño posteriores solo se aplicarán a los dispositivos con (wR hR) hasta que hagamos clic en Listo para variar .Conclusión
Vimos que los Storyboards tienen sus fortalezas y debilidades. Mi opinión es que no debe negarse por completo a usarlos. Cuando se usan correctamente, brindan grandes beneficios y ayudan a resolver tareas de manera efectiva. Solo necesita aprender a priorizar y olvidar argumentos como "No me gustan los guiones gráficos" o "Estoy acostumbrado a hacer esto".