ios - raywenderlich - Restricciones rotas de UIStackViews anidadas
uistackview swift programmatically (6)
Tengo una jerarquía de vista compleja, construida en Interface Builder, con UIStackViews anidados. Recibo avisos de "restricciones no satisfactorias" cada vez que oculto algunas de mis vistas de pila internas. Lo he rastreado hasta esto:
(
"<NSLayoutConstraint:0x1396632d0 ''UISV-canvas-connection'' UIStackView:0x1392c5020.top == UILabel:0x13960cd30''Also available on iBooks''.top>",
"<NSLayoutConstraint:0x139663470 ''UISV-canvas-connection'' V:[UIButton:0x139554f80]-(0)-| (Names: ''|'':UIStackView:0x1392c5020 )>",
"<NSLayoutConstraint:0x139552350 ''UISV-hiding'' V:[UIStackView:0x1392c5020(0)]>",
"<NSLayoutConstraint:0x139663890 ''UISV-spacing'' V:[UILabel:0x13960cd30''Also available on iBooks'']-(8)-[UIButton:0x139554f80]>"
)
Específicamente, la UISV-spacing
: cuando oculta un UIStackView, su alta restricción obtiene una constante de 0, pero parece que está en conflicto con la restricción de espaciado de la vista de pila interna: requiere 8 puntos entre mi Etiqueta y el Botón, lo cual es irreconciliable con la restricción de ocultación y por lo que las restricciones se estrellan.
¿Hay alguna forma de evitar esto? He intentado ocultar de forma recursiva todas las vistas de pila internas de la vista de la pila oculta, pero eso da lugar a animaciones extrañas en las que el contenido flota fuera de la pantalla y provoca que caigan FPS graves, sin que se solucione el problema.
Aquí está la implementación de la sugerencia # 3 de Senseful escrita como clase Swift 3 usando restricciones SnapKit. También intenté anular las propiedades, pero nunca conseguí que funcionara sin advertencias, así que me quedo con envolver UIStackView:
class NestableStackView: UIView {
private var actualStackView = UIStackView()
override init(frame: CGRect) {
super.init(frame: frame);
addSubview(actualStackView);
actualStackView.snp.makeConstraints { (make) in
// Lower edges priority to allow hiding when spacing > 0
make.edges.equalToSuperview().priority(999);
}
}
convenience init() {
self.init(frame: CGRect.zero);
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addArrangedSubview(_ view: UIView) {
actualStackView.addArrangedSubview(view);
}
func removeArrangedSubview(_ view: UIView) {
actualStackView.removeArrangedSubview(view);
}
var axis: UILayoutConstraintAxis {
get {
return actualStackView.axis;
}
set {
actualStackView.axis = newValue;
}
}
open var distribution: UIStackViewDistribution {
get {
return actualStackView.distribution;
}
set {
actualStackView.distribution = newValue;
}
}
var alignment: UIStackViewAlignment {
get {
return actualStackView.alignment;
}
set {
actualStackView.alignment = newValue;
}
}
var spacing: CGFloat {
get {
return actualStackView.spacing;
}
set {
actualStackView.spacing = newValue;
}
}
}
Entonces, tienes esto:
Y el problema es que cuando colapsas por primera vez la pila interna, obtienes errores de diseño automático:
2017-07-02 15:40:02.377297-0500 nestedStackViews[17331:1727436] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don''t want.
Try this:
(1) look at each constraint and try to figure out which you don''t expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x62800008ce90 ''UISV-canvas-connection'' UIStackView:0x7fa57a70fce0.top == UILabel:0x7fa57a70ffb0''Top Label of Inner Stack''.top (active)>",
"<NSLayoutConstraint:0x62800008cf30 ''UISV-canvas-connection'' V:[UILabel:0x7fa57d30def0''Bottom Label of Inner Sta...'']-(0)-| (active, names: ''|'':UIStackView:0x7fa57a70fce0 )>",
"<NSLayoutConstraint:0x62000008bc70 ''UISV-hiding'' UIStackView:0x7fa57a70fce0.height == 0 (active)>",
"<NSLayoutConstraint:0x62800008cf80 ''UISV-spacing'' V:[UILabel:0x7fa57a70ffb0''Top Label of Inner Stack'']-(8)-[UILabel:0x7fa57d30def0''Bottom Label of Inner Sta...''] (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x62800008cf80 ''UISV-spacing'' V:[UILabel:0x7fa57a70ffb0''Top Label of Inner Stack'']-(8)-[UILabel:0x7fa57d30def0''Bottom Label of Inner Sta...''] (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
El problema, como notó, es que la vista de pila externa aplica una restricción de altura = 0 a la vista de pila interna. Esto entra en conflicto con la restricción de relleno de 8 puntos aplicada por la vista de pila interna entre sus propias subvistas. Ambas restricciones no pueden ser satisfechas simultáneamente.
La vista de la pila externa usa esta restricción de altura = 0, creo, porque se ve mejor cuando está animada que simplemente dejando que la vista interna se oculte sin encogerse primero.
Hay una solución simple para esto: envolver la vista de pila interna en una vista UIView
simple y ocultar ese contenedor. Voy a demostrar
Aquí está el esquema de la escena para la versión rota arriba:
Para solucionar el problema, seleccione la vista de pila interna. Desde la barra de menú, elija Editor> Incrustar en> Ver:
Interface Builder creó una restricción de ancho en la vista del contenedor cuando hice esto, así que elimine esa restricción de ancho:
A continuación, cree restricciones entre los cuatro bordes de la envoltura y la vista de pila interna:
En este punto, el diseño es realmente correcto en tiempo de ejecución, pero Interface Builder lo dibuja incorrectamente. Puede solucionarlo al establecer las prioridades de abrazos verticales de los hijos de la pila interna más altas. Los puse a 800:
En este momento, no hemos solucionado el problema de restricciones insatisfactorias. Para hacerlo, encuentre la restricción inferior que acaba de crear y establezca su prioridad en menos de lo requerido. Vamos a cambiarlo a 800:
Finalmente, probablemente tenía una salida en su controlador de vista conectado a la vista de pila interna, porque estaba cambiando su propiedad hidden
. Cambie esa salida para conectarse a la vista de contenedor en lugar de la vista de pila interna. Si el tipo de su tienda es UIStackView
, deberá cambiarlo a UIView
. El mío ya era de tipo UIView
, así que lo volví a conectar en el guión gráfico:
Ahora, cuando alterne la propiedad hidden
la vista de contenedor, la vista de pila parecerá colapsar, sin advertencias de restricciones insatisfiables. Parece prácticamente idéntico, por lo que no me molestaré en publicar otro GIF de la aplicación en ejecución.
Puedes encontrar mi proyecto de prueba en este repositorio de github .
Este es un problema conocido con la ocultación de vistas de pila anidadas.
Hay esencialmente 3 soluciones a este problema:
- Cambie el espaciado a 0, pero luego necesitará recordar el valor de espaciado anterior.
- Llame a
innerStackView.removeFromSuperview()
, pero luego necesitará recordar dónde insertar la vista de pila. - Envuelva la vista de pila en una vista UIV con al menos una restricción de 999. Por ejemplo, top @ 1000, líder @ 1000, seguimiento @ 1000, inferior @ 999.
La tercera opción es la mejor en mi opinión. Para obtener más información sobre este problema, por qué sucede, las diferentes soluciones y cómo implementar la solución 3, vea mi respuesta a una pregunta similar .
Lo ideal sería que pudiéramos establecer la prioridad de la UISV-spacing
en un valor más bajo, pero no parece haber ninguna manera de hacerlo. :)
Estoy teniendo éxito al establecer la propiedad de spacing
de las vistas de pila anidadas en 0 antes de ocultarlo, y restaurarlas al valor adecuado después de volver a hacerlas visibles.
Creo que hacer esto recursivamente en vistas de pila anidadas funcionaría. Puede almacenar el valor original de la propiedad de spacing
en un diccionario y restaurarlo más tarde.
Mi proyecto solo tiene un nivel de anidamiento, por lo que no estoy seguro de si esto resultaría en problemas de FPS. Mientras no animes los cambios en el espaciado, no creo que eso genere demasiado éxito.
Me topé con un problema similar con la ocultación de UISV. Para mí, la solución fue reducir las prioridades de mis propias restricciones de Requerido (1000) a algo menos que eso. Cuando se agregan restricciones de ocultación de UISV, tienen prioridad y las restricciones ya no entran en conflicto.
Otro enfoque
Trate de evitar UIStackViews anidados. Los amo y construyo casi todo con ellos. Pero al reconocer que agregan restricciones en secreto, trato de usarlas solo en el nivel más alto y no están anidadas siempre que sea posible. De esta manera puedo especificar la segunda prioridad más alta .defaultHigh
a la restricción de espacio que resuelve mis advertencias.
Esta prioridad es suficiente para evitar la mayoría de los problemas de diseño.
Por supuesto, necesita especificar algunas restricciones más, pero de esta manera usted tiene control total de ellas y hacer explícito el diseño de su vista.