AutoLayout: removeFromSuperview / removeConstraints arroja una excepción y se cuelga duro

Usamos restricciones de diseño automático de forma selectiva, principalmente para colocar tags en relación con los elementos de campo editables (UITextView, UITextField, por lo general). Sin embargo, desde que implementamos el diseño automático para estos campos, estamos viendo una desagradable excepción y falla cada vez que descargamos vistas, desasignación, etc. Las excepciones están sucediendo ya que está intentando eliminar las restricciones de una vista antes de descargarla.

Nuestra jerarquía de vista / controlador es como tal:

UITableViewController (plain style, but with cell appearance to mimic grouped style) --> UITableViewCell ----> UIViewController (container for editable form) ------> UICollectionViewController (editable form) --------> UICollectionViewCell -----------> UIViewController (editable field) --------------> UILabel (field label) **HAS CONSTRAINTS** --------------> UITextView / UITextField (field value) **HAS CONSTRAINTS** 

Muchas veces, cuando las celdas de la tabla del nivel superior se desasignan / reemplazan / vuelven a cargar, vemos una gran excepción y luego se cuelga al intentar desasignar / descargar la jerarquía de vista dentro.

Intenté mitigar el locking detectando la excepción (sin ayuda) y eliminando todas las restricciones de la vista afectada y de todas las subvistas antes de la desasignación / descarga (en vista viewWillDisappear: ) y no parece ayudar. Incluso he tratado de eliminar estas restricciones una por una para ver si hay una en particular que está causando el problema, pero todas están explotando cuando llamamos removeConstraint: o removeConstraints: en un contenedor en preparación para desaparecer.

¡Estoy desconcertado! Aquí hay un fragmento de nuestra excepción: aproximadamente 3000 líneas han sido cortadas, así que si necesita más, solo pregunte.

 Exception while deallocating view: { Rows: 0x18911270.posErrorMarker == 4 + 1*0x18911270.negError + 1*0x189112f0.marker + -1*0x189113f0.negError + 1*0x189113f0.posErrorMarker + 1*0x18911a60.marker + -0.5*0x1892dae0.negError + 0.5*0x1892dae0.posErrorMarker + 1*0x18951520.negError + -1*0x18951520.posErrorMarker + -0.5*0x18958090.negError + 0.5*0x18958090.posErrorMarker 0x189112b0.negError == 12 + 1*0x189112b0.posErrorMarker + -1*0x189112f0.marker + 1*0x189113f0.negError + -1*0x189113f0.posErrorMarker + -1*0x18911a60.marker + 1*0x18925530.marker + 0.5*0x1892dae0.negError + -0.5*0x1892dae0.posErrorMarker + 1*0x1893e080.marker + 0.5*0x18958090.negError + -0.5*0x18958090.posErrorMarker + 1*0x18963640.marker 0x18911370.negError == 9 + -1*0x189112f0.marker + 1*0x18911370.posErrorMarker + 1*0x18925530.marker + 1*0x1892dae0.negError + -1*0x1892dae0.posErrorMarker + 1*0x1893e080.marker + 1*0x18963640.marker 0x189113b0.slackMarker == 2 + -1*0x189107d0.marker + 1*0x18910b90.negError + -1*0x18910b90.posErrorMarker + ........ EXPLETIVES DELETED ......... UITableView:0xca2b000.contentHeight == 36 + 1*0xc221c00.marker UITableView:0xca2b000.contentWidth == 704 + 1*0xc239470.marker UITableView:0xca2b000.minX == 0 + 1*0xc2a23f0.marker + -0.5*0xc2a2590.marker UITableView:0xca2b000.minY == 0 + 1*0xc2a25d0.marker + -0.5*0xc2a2630.marker UITableViewCellContentView:0x18ab13d0.Height == 174 + 1*0x18abd4f0.marker UITableViewCellContentView:0x18ab13d0.Width == 704 + 1*0x18abd470.marker ........ EXPLETIVES DELETED .........  Marker:0x18988bc0.marker  Marker:0x18994b40.marker  Marker:0x18998480.marker  Marker:0x18aae320.marker  Marker:0x18aae410.marker  Marker:0x18aae450.marker ........ EXPLETIVES DELETED .........  Marker:0xc2de2f0.marker  Marker:0xc2de3b0.marker  Marker:0xc2de430.marker  Marker:0xc2de520.marker  Marker:0xc2de560.marker ........ EXPLETIVES DELETED .........  Marker:0xc2f5730.posErrorMarker  Marker:0xc2f5730.posErrorMarker  Marker:0xc2f5770.posErrorMarker internal error. Cannot find an outgoing row head for incoming head UIView:0x189712b0.Width, which should never happen.' 
 /**** BEGIN Individual Field Controller - This code is from the base individual field controller used in our editable form collection *****/ - (void)viewDidLoad { [super viewDidLoad]; self.view.clipsToBounds = YES; self.view.opaque = YES; CGRect viewFrame = self.view.frame; viewFrame.size = [self defaultFieldSize]; self.view.frame = viewFrame; if (self.backgroundColor) { self.view.backgroundColor = self.backgroundColor; } else { self.view.backgroundColor = [UIColor whiteColor]; } [self createLabelAndField]; [self setLabelAndFieldContraints]; [self.view addConstraints:self.labelValueConstraints]; [self.view setNeedsUpdateConstraints]; } - (void)createLabelAndField { [self removeLabelAndField]; UILabel *label = [[UILabel alloc] init]; label.font = self.labelFont; label.textColor = self.labelColor; label.lineBreakMode = NSLineBreakByWordWrapping; label.textAlignment = NSTextAlignmentLeft; label.adjustsFontSizeToFitWidth = NO; label.numberOfLines = 0; if (self.backgroundColor) { label.backgroundColor = self.backgroundColor; } else { label.backgroundColor = [UIColor whiteColor]; } [self.view addSubview:label]; self.label = label; /// EXAMPLE valueView initialization from a subclass that handles long text TapEditableTextView *textView = [[TapEditableTextView alloc] init]; if (self.hasLabelOverValue) { textView.shouldMimicTextField = NO; } else { textView.shouldMimicTextField = YES; } textView.delegate = self; textView.keyboardType = UIKeyboardTypeDefault; textView.font = self.valueFont; textView.textColor = self.valueColor; textView.textAlignment = NSTextAlignmentLeft; textView.normalBackgroundColor = self.backgroundColor; textView.editable = NO; textView.textLines = self.textLines; self.valueTextView = textView; self.valueView = textView; [self.view addSubview:textView]; } - (void)removeLabelAndField { [self clearConstraints]; if (self.label) { [self.label removeFromSuperview]; self.label = nil; } if (self.valueView) { [self.valueView removeFromSuperview]; self.valueView = nil; } } - (void)clearConstraints { if (self.isViewLoaded && self.labelValueConstraints) { [self.view removeConstraints:self.labelValueConstraints]; } self.labelValueConstraints = nil; self.labelToValueHorizConstraint = nil; self.valueWidthConstraint = nil; } // This is called in our field's viewDidLoad, after we've created our label and valueView (UITextField, UITextView, etc) - (void)setLabelAndFieldContraints { [self clearConstraints]; self.labelValueConstraints = [NSMutableArray array]; self.label.translatesAutoresizingMaskIntoConstraints = NO; self.valueView.translatesAutoresizingMaskIntoConstraints = NO; NSLayoutConstraint *constraint = nil; constraint = [NSLayoutConstraint constraintWithItem:self.label attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0f constant:self.labelValueGap]; constraint.priority = UILayoutPriorityRequired; [self.labelValueConstraints addObject:constraint]; constraint = [NSLayoutConstraint constraintWithItem:self.label attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f constant:0]; constraint.priority = 550; [self.labelValueConstraints addObject:constraint]; constraint = [NSLayoutConstraint constraintWithItem:self.label attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0]; constraint.priority = 400; [self.labelValueConstraints addObject:constraint]; constraint = [NSLayoutConstraint constraintWithItem:self.valueView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f constant:0]; constraint.priority = UILayoutPriorityRequired; [self.labelValueConstraints addObject:constraint]; constraint = [NSLayoutConstraint constraintWithItem:self.valueView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0]; constraint.priority = 499; [self.labelValueConstraints addObject:constraint]; constraint = [NSLayoutConstraint constraintWithItem:self.valueView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0f constant: -(kDisclosureWidth + self.labelValueGap) ]; constraint.priority = 901; [self.labelValueConstraints addObject:constraint]; constraint = [NSLayoutConstraint constraintWithItem:self.valueView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:self.label attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:self.labelValueGap]; constraint.priority = UILayoutPriorityDefaultHigh + 1; [self.labelValueConstraints addObject:constraint]; self.labelToValueHorizConstraint = constraint; constraint = [NSLayoutConstraint constraintWithItem:self.label attribute:NSLayoutAttributeBaseline relatedBy:NSLayoutRelationEqual toItem:self.valueView attribute:NSLayoutAttributeBaseline multiplier:1.0f constant:0.f]; constraint.priority = 600; [self.labelValueConstraints addObject:constraint]; constraint = [NSLayoutConstraint constraintWithItem:self.valueView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:(1.f - self.labelWidthPercentage) constant:0]; constraint.priority = 305; [self.labelValueConstraints addObject:constraint]; self.valueWidthConstraint = constraint; [self setCompressionAndHuggingForLabelView:self.label]; [self setCompressionAndHuggingForValueView:self.valueView]; } - (void)setCompressionAndHuggingForLabelView:(UILabel *)labelView { if (!labelView) { return; } [labelView setContentCompressionResistancePriority:510 forAxis:UILayoutConstraintAxisHorizontal]; [labelView setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical]; [labelView setContentHuggingPriority:450 forAxis:UILayoutConstraintAxisHorizontal]; [labelView setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical]; } - (void)setCompressionAndHuggingForValueView:(UIView *)valueView { if (!valueView) { return; } [valueView setContentCompressionResistancePriority:509 forAxis:UILayoutConstraintAxisHorizontal]; [valueView setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical]; [valueView setContentHuggingPriority:300 forAxis:UILayoutConstraintAxisHorizontal]; [valueView setContentHuggingPriority:650 forAxis:UILayoutConstraintAxisVertical]; } /****** END Individual Field Controller ******/ 

Tuve una (extensa) conversación con un ingeniero de Apple sobre este accidente.

Estas son las dos causas más probables:

  1. Tiene una restricción no válida, como view1.left = view2.left + 20 donde view2 es inesperadamente nulo o tiene un multiplicador de 0. Asegúrese de duplicar (y triplicar) las restricciones para asegurarse de que sean correctas. Aquí hay 2 ejemplos de restricciones problemáticas:

     // The first constraint would be a problem if view2 were nil [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeBottom multiplier:1 constant:20]; // The second constraint is a problem because the 0 multiplier causes view2 to be "lost" [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeBottom multiplier:0 constant:5]; 
  2. Se está produciendo un error en el motor interno de diseño automático de Foundation relacionado con la pérdida acumulada de precisión de coma flotante. Cuando se ha bloqueado, la forma en que puede saber que este es el caso es buscar a través del registro de excepción (grande) en la consola un número flotante muy pequeño (casi cero) como este:

<505:-7.45058e-08>*PWPlotLegendEntryView:0x600000582be0.Height{id: 34609} +

(Busque e- en la salida de la consola para encontrar números pequeños como este.) Este número ( -7.45058e-08 en este caso) representa el coeficiente en este punto particular en el tiempo mientras el motor interno está resolviendo restricciones. En este caso, se supone que el número es exactamente 0, pero debido a la forma en que el motor de diseño automático realiza cálculos con números de coma flotante, se ha convertido en un número negativo extremadamente pequeño que lo hace explotar todo. Si puedes encontrar ese número en la salida, sabes que has golpeado este error.

¿Cómo puedes solucionar este problema?

Cambiar el orden en que agrega restricciones (activar) puede terminar cambiando el orden de los cálculos en el motor interno, lo que como resultado puede hacer que este problema desaparezca a medida que se realizan las operaciones matemáticas sin ninguna pérdida problemática de precisión.

Este problema parece surgir con mayor frecuencia cuando ha cambiado la resistencia a la compresión de contenido o las prioridades de contenido para las vistas, así que trate de comentar cualquier código que lo haga para ver si está causando este error, o reordenándolo antes. o más tarde en tu código de configuración de restricciones.

Más detalles sobre mi caso específico:

Me encontré con este locking en iOS. Los pasos para reproducirlo fueron bastante interesantes:

  1. Un controlador de vista que contiene una vista de tabla se presionó en la pantalla (en un controlador de navegación).
  2. La vista de tabla tenía que contener suficientes células para que no encajaran en el área visible, luego tenía que desplazarse a la última celda y luego retroceder un poco (presumiblemente, esto estaba causando que las células se reutilizaran, lo que estaba desencadenando este problema).
  3. Luego, cuando el controlador de vista que contiene la vista de tabla se saltó de la stack de navegación, inmediatamente después de completar la animación pop, la aplicación se colgaría en el punto donde la vista del controlador de vista se eliminó de la jerarquía de vista.

Después de muchas pruebas y errores, pude aislar el problema en una cosa específica: establecer la resistencia a la compresión del contenido y las prioridades de aarm para un UIImageView en cada una de las celdas de la vista de tabla. En este caso, la vista de la imagen se posiciona utilizando Diseño automático dentro de la celda, y para lograr el diseño correcto, la vista de la imagen debe ser exactamente su tamaño de contenido intrínseco (el tamaño de su imagen).

Este fue el código problemático:

 // Inside of the UITableViewCell's updateConstraints method... [self.imageView setContentCompressionResistancePriority:​UILayoutPriorityRequired forAxis:​UILayoutConstraintAxisHorizontal]; [self.imageView setContentCompressionResistancePriority:​UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; [self.imageView setContentHuggingPriority:​UILayoutPriorityRequired forAxis:​UILayoutConstraintAxisHorizontal]; [self.imageView setContentHuggingPriority:​UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; 

Al eliminar el código anterior y reemplazarlo con 2 restricciones (en Prioridad requerida) para arreglar el ancho y alto de la vista de la imagen con el tamaño de la imagen se obtuvo el mismo resultado, pero se evitó el locking. Aquí está el código de reemplazo (usando PureLayout ):

 [self.imageView autoSetDimensionsToSize:self.imageView.image.size]; 

También encontré que el solo hecho de mover las 4 líneas problemáticas a un lugar diferente en mi código de configuración de restricciones resolvió el problema, presumiblemente porque esto cambió el orden de los cálculos lo suficiente como para evitar la pérdida problemática de precisión.

Problema de desasignación: una posibilidad

El código que funciona con el diseño automático bien puede ejecutarse en el hilo principal, pero uno de los bloques que se ejecuta en segundo plano y utiliza su vista (quizás indirectamente) puede contener una referencia fuerte de la vista o de uno de sus propietarios como controlador de vista (eso es el comportamiento predeterminado de los bloques Objective-C). Cuando dicho bloque se ejecuta y se desasigna en una cola de fondo, las referencias fuertes que captura se liberan en esa misma cola y puede enfrentar un problema conocido de desasignación .

  1. En su controlador de vista, asegúrese de estar utilizando una referencia débil a self en todos los bloques que no necesitan una referencia fuerte (y pueden ejecutarse en segundo plano). Puedes declararlo así: __weak typeof(self) weakSelf = self; antes del bloque – y usa weakSelf dentro del bloque.

  2. Lo mismo ocurre con las variables locales que contienen referencias a sus vistas: asegúrese de que sus valores se capturen como referencias débiles.

Otra posibilidad

En mi trabajo, me encontré con un problema similar en iOS 6 cuando la vista oculta participó en el diseño. Al eliminar la vista de la jerarquía ( -[UIView removeFromSuperview] ) en lugar de configurar la propiedad hidden en SÍ, se solucionó el problema.

Tuve el mismo problema, lo resolví eliminando restricciones de a una por vez en IB hasta que se solucionó el locking. Esto se redujo a la restricción ofensiva. Luego reinstalé dicha restricción, pero revirtí los ítems:

enter image description here

Puede ser tan afortunado y ser capaz de resolver sus problemas AL tan fácilmente.

Para cualquier persona que encuentre este problema en cualquier versión de iOS> 8.0, Apple indica que usa la propiedad “activa” en NSLayoutConstraint en lugar de las funciones removeConstraint / addConstraint en UIView. Apple Docs addConstraint reference

Para hacer que la increíble respuesta de @ smileyborg sea más procesable:

Esto puede suceder si tiene restricciones con los multiplicadores que podrían sufrir problemas de precisión de coma flotante.

Resolver:

  1. Repase todas las restricciones que tienen multiplicadores (en código de diseño o editando manualmente un guión gráfico / plumilla y buscando multiplier= ).
  2. Si el multiplicador no es una potencia “bonita” de dos flotadores, gírelo al más cercano (puede usar una calculadora de coma flotante )

Una forma fácil de hacer 2 es ingresar el número que usted desea y la calculadora y luego desactivar los bits de menor precisión en la mantisa hasta que el valor coincida con el valor decimal redondeado en la parte inferior de la calculadora.

En mi caso, era una restricción de ancho proporcional con un multiplicador de 8: 9. Lo cambié a 7: 9 y funcionó.

Por cierto, la forma más fácil de encontrar una restricción es comenzar a eliminar vistas del controlador de vista. Hazlo usando el algoritmo binario 🙂 eliminando la mitad de las vistas, luego la mitad del medio que hace que la aplicación se cuelgue, etc.

Para mí, el problema era que estaba eliminando una restricción a la vez que me convenía, después de llamar a dequeueReusableCellWithReuseIdentifier mientras establecía las propiedades de mi UICollectionViewCell. La solución fue llamar en su lugar:

  [_myUICollectionViewCell setNeedsUpdateConstraints]; 

y anular:

  -(void)updateConstraints 

y hacer mi lío allí. Parece que no puedes simplemente eliminar las restricciones cuando lo desees.

Acabo de tropezar con el mismo error bajo OSX Mavericks con una aplicación OSX que estoy desarrollando, pero a diferencia de las otras respuestas dadas, definitivamente no tengo otros hilos que interactúen con los objetos UI, y la jerarquía de vista en cuestión es definitivamente visible también. Yo tampoco estoy usando bloques. Extrañamente, el problema desapareció cuando eliminé una restricción vertical en un NSTextField.

FWIW la vista problemática cuya eliminación de su supervista causa el error “error interno. No se puede encontrar un encabezado saliente saliente para encabezado entrante” es uno de los muchos controles del panel lateral que juntos presentan las propiedades de los objetos en la vista principal que se pueden cortar, copiado, creado, etc. Esto significa que el usuario puede pegar objetos nuevos en la vista principal con bastante rapidez, lo que significa que los controles del panel lateral se destruyen y también se crean nuevos muy rápidamente. Por supuesto, con todo en el hilo principal, esto no debería marcar la diferencia, pero parece que sí.

La restricción exacta que causa problemas fue

[auto addConstraint: [NSLayoutConstraint constraintWithItem: control attribute: NSLayoutAttributeHeight relatedBy: NSLayoutRelationEqual toItem: otro atributo: NSLayoutAttributeHeight multiplicador: constante 1.4: 0.0]];

donde el control es el NSTextField (editable) que causa problemas, y ‘otro’ es otra etiqueta NSTextField (no editable).

MZFormSheetController este problema con el pod MZFormSheetController : https://github.com/m1entus/MZFormSheetController/issues/78

Este código falla:

 [formSheetController.view addSubview:self.sharePanel]; // ... [self.sharePanel removeFromSuperview]; // <-- CRASHES HERE 

Mi solución es muy extraña pero funciona:

 [self.sharePanel removeFromSuperview]; // <-- This line helps to avoid crash [formSheetController.view addSubview:self.sharePanel]; // ... [self.sharePanel removeFromSuperview]; 

Y aquí está la statement de propiedad de sharePanel :

 @property (weak, nonatomic) IBOutlet UIView *sharePanel; 

Estoy recibiendo este locking cuando llamo removeConstraints: con un argumento nulo.

Obtuve este locking cuando todavía tengo una restricción faltante en el modo wAnyhAny, corrigiendo esto eliminó el error.

Como las otras respuestas en este hilo indican que esto es de alguna manera un problema de autodiseño / restricción inválido, aunque parece ser muy quisquilloso sobre lo que califica como “inválido”.

Afortunadamente, no había hecho muchos cambios desde mi último compromiso y pude rastrear los cambios ofensivos. Para mí, tener una vista de 10 UIImageView horizontal con el mismo ancho y una relación de aspecto fija de 2: 3 fue el problema.

El locking solo pareció ocurrir después de salir del UIViewController que contenía esta fila de imágenes. Cada UIImageView se configuró en UIViewContentModeScaleAspectFill . La eliminación de este cambio de modo de contenido (que se realizó antes de que se establecieran los UIImage ) pareció solucionar mi problema, pero no era una solución aceptable. Terminé eliminando la restricción de relación de aspecto y simplemente usando un ancho y alto fijo para cada imagen.

No sé por qué se estrelló mi aplicación … El locking también SÓLO podría reproducirse en un iPhone 4s con iOS 7.1.2. Traté de reproducir la misma falla en un simulador de iPhone 4s que ejecuta iOS 9.1 sin éxito. Tampoco se bloqueará cuando se ejecuta en un iPhone 5 phsyical con iOS 9.1.

Espero que ayude a alguien por ahí

De acuerdo con la documentación de Apple:

Al desarrollar para iOS 8.0 o posterior, establezca la propiedad activa de la restricción en YES en lugar de llamar directamente al método addConstraint: La propiedad activa automáticamente agrega y elimina la restricción de la vista correcta.

En mi caso, tuve que modificar la restricción de ancho

 for var constraint in self.navigationBar.constraints { if constraint.identifier == "theProgressWidth" { let sizeWidth = self.navigationBar.frame.size.width constraint = NSLayoutConstraint(item: progress!, attribute: .Width, relatedBy: .Equal, toItem: self.navigationBar, attribute: .Width, multiplier: ((sizeWidth * (level / 100)) / sizeWidth), constant: 0) constraint.active = true } }