Desbloquear automáticamente las subvistas de un `setFrame:` ​​UIView

Tengo una UIView contenedor. Las posiciones de sus hijos se manejan con autolayout. Sin embargo, quiero ubicar el contenedor de manera explícita .

El contenedor existe dentro de una subclass UIView que llamo VideoPreviewView . El contenedor se denomina contentOverlayView .

Aquí está mi código para agregar un niño rojo obvio al contenedor que lo llena, a exception de un margen de 1 píxel en los bordes:

 UIView *const networkingView = [UIView new]; [networkingView setTranslatesAutoresizingMaskIntoConstraints: NO]; [networkingView setBackgroundColor: [UIColor networkingColor]]; [[videoView contentOverlayView] addSubview: networkingView]; [[videoView contentOverlayView] addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(1@900)-[networkingView]-(1@900)-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(networkingView)]]; [[videoView contentOverlayView] addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(1@900)-[networkingView]-(1@900)-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(networkingView)]]; 

Establecer marco en layoutSubviews

Mi enfoque inicial para mantener la vista del contenedor posicionado en el interior era simplemente anular layoutSubviews para VideoPreviewView y establecer explícitamente el marco del contenedor allí, así:

 -(void) layoutSubviews { // Other stuff snipped out. const CGRect videoBounds = [self videoBounds]; [_contentOverlayView setFrame: videoBounds]; [super layoutSubviews]; } 

… Pero esto no funcionó. Si utilizo videoView en videoView obtengo:

 <VideoPreviewView: 0x146a54f0; frame = (0 0; 320 460); layer = <CALayer: 0x146a5640>> | <UIView: 0x145b6610; frame = (0 0; 2 2); layer = <CALayer: 0x145b6680>> | | <UIView: 0x145bff60; frame = (1 1; 0 0); layer = <CALayer: 0x145bffd0>> 

Tiene el ancho suficiente para respetar los márgenes que requería, pero la position es completamente incorrecta, y el marco no es tan grande como deseo.

Utilicé _autoLayoutTrace en la vista del contenedor para intentar resolver esto y me dijeron que el layout es ambiguo:

 UIWindow:0x145a3a50 | UIView:0x145a6e60 | | HPBezelView:0x1468d580 | | | UIImageView:0x145aab30 | | *VideoPreviewView:0x146a54f0 | | | *UIView:0x145b6610- AMBIGUOUS LAYOUT for UIView:0x145b6610.minX{id: 5}, UIView:0x145b6610.minY{id: 13}, UIView:0x145b6610.Width{id: 8}, UIView:0x145b6610.Height{id: 16} | | | | *UIView:0x145bff60- AMBIGUOUS LAYOUT for UIView:0x145bff60.minX{id: 4}, UIView:0x145bff60.minY{id: 12}, UIView:0x145bff60.Width{id: 9}, UIView:0x145bff60.Height{id: 17} 

Esto me sugirió que si utilizo la reproducción automática, se ignora el marco, así que también necesito configurar el marco del contenedor utilizando la reproducción automática.

Agregar restricciones de tamaño

Algunas otras publicaciones y blogs y tales también sugieren este curso de acción. Entonces agregué algunas restricciones para controlar el ancho y la altura del contenedor:

 _contentOverlayWidthConstraint = [NSLayoutConstraint constraintWithItem: contentOverlayView attribute: NSLayoutAttributeWidth relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 1.0 constant: 1]; _contentOverlayHeightConstraint = [NSLayoutConstraint constraintWithItem: contentOverlayView attribute: NSLayoutAttributeHeight relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 1.0 constant: 1]; [contentOverlayView addConstraints: @[_contentOverlayWidthConstraint, _contentOverlayHeightConstraint]]; 

layoutSubviews mi método layoutSubviews para ajustar las "constantes" en estas restricciones:

 -(void) layoutSubviews { const CGRect videoBounds = [self videoBounds]; [_contentOverlayView setFrame: videoBounds]; [_contentOverlayWidthConstraint setConstant: CGRectGetWidth(videoBounds)]; [_contentOverlayHeightConstraint setConstant: CGRectGetHeight(videoBounds)]; [super layoutSubviews]; } 

Esto funciona un poco mejor: la vista roja ahora es visible y tiene el tamaño correcto, pero aún se encuentra en el lugar equivocado. Todavía obtengo ambigüedades de la huella:

  UIWindow:0x17d76a00 | UIView:0x17d7dfd0 | | HPBezelView:0x17d7d7c0 | | | UIImageView:0x17d82240 | | *VideoPreviewView:0x17d925e0 | | | UIView:0x17d96790 | | | *UIView:0x17d96830- AMBIGUOUS LAYOUT for UIView:0x17d96830.minX{id: 9}, UIView:0x17d96830.minY{id: 16} | | | | *UIView:0x17d9ae90- AMBIGUOUS LAYOUT for UIView:0x17d9ae90.minX{id: 8}, UIView:0x17d9ae90.minY{id: 15} 

… entonces, las ambigüedades de ancho y alto se han ido, pero las ambigüedades de position permanecen.

Agregar restricciones de position

Entonces, agregué dos restricciones de layout más para establecer la position del contenedor:

 _contentOverlayLeftConstraint = [NSLayoutConstraint constraintWithItem: contentOverlayView attribute: NSLayoutAttributeLeft relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 1.0 constant: 0]; _contentOverlayTopConstraint = [NSLayoutConstraint constraintWithItem: contentOverlayView attribute: NSLayoutAttributeTop relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 1.0 constant: 0]; [contentOverlayView addConstraints: @[_contentOverlayLeftConstraint, _contentOverlayTopConstraint]]; 

Y layoutSubviews actualizado:

 -(void) layoutSubviews { [[self contentView] setFrame: [self bounds]]; [[self videoPreviewLayer] setFrame: [self bounds]]; const CGRect videoBounds = [self videoBounds]; [_contentOverlayWidthConstraint setConstant: CGRectGetWidth(videoBounds)]; [_contentOverlayHeightConstraint setConstant: CGRectGetHeight(videoBounds)]; [_contentOverlayLeftConstraint setConstant: CGRectGetMinX(videoBounds)]; [_contentOverlayTopConstraint setConstant: CGRectGetMinY(videoBounds)]; [super layoutSubviews]; } 

Choque

Pero cuando trato de agregar las nuevas restricciones, obtengo una exception con el error:

* Terminar la aplicación debido a la exception no detectada 'NSInvalidArgumentException', razón: '* + [NSLayoutConstraint constraintWithItem: atributo: relatedBy: toItem: atributo: multiplicador: constante:]: no se puede establecer una restricción que establezca una location igual a una constante. Los attributes de location deben especificarse en pares '

Esto no lo consigo en absoluto. No puedo ver cómo puedo rectificar esto.

¿Puede usted ayudar?

El problema es estas líneas y similares:

 [NSLayoutConstraint constraintWithItem: contentOverlayView attribute: NSLayoutAttributeLeft relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 1.0 constant: 0]; 

No puede establecer Left / Top igual a un elemento nulo. Debe establecerlo en algún atributo de otra vista (por lo general, sería la parte izquierda / superior de la vista de supervisión de este elemento, o algo con lo que desee alinearlo).

Solo el ancho y la altura pueden ser absolutos. E incluso ellos no pueden ser 0 (o al less no deberían serlo), como lo tienes aquí; eso no tiene sentido.

Matt ha proporcionado una gran respuesta aquí. Me gustaría agregar algunos puntos más de mi implementación que he encontrado útiles.

Evitar infracciones de restricciones iniciales.

Como señala Matt, creo mis restricciones de manera que la vista tenga 0 ancho y alto durante el init de la vista. Lo hago porque cuando creo las restricciones no tengo una idea sensata sobre el ancho o la altura. Desafortunadamente, si agrego estas restricciones a una vista, es probable que causen una infracción de restricción.

Para evitar esto, init no agrega las restricciones a una vista. Espero hasta la primera vez que se llama a updateConstraints . Aquí, sé los valores correctos para el ancho y el alto, así que los puse. Luego agrego las restricciones a las vistas si aún no lo hice. Se parece a esto:

 -(void) updateConstraints { const CGRect videoBounds = [self videoBounds]; [_contentOverlayWidthConstraint setConstant: CGRectGetWidth(videoBounds)]; [_contentOverlayHeightConstraint setConstant: CGRectGetHeight(videoBounds)]; [_contentOverlayLeftConstraint setConstant: CGRectGetMinX(videoBounds)]; [_contentOverlayTopConstraint setConstant: CGRectGetMinY(videoBounds)]; if(!_constraintsAreAdded) { [_contentOverlayView addConstraints: @[_contentOverlayWidthConstraint, _contentOverlayHeightConstraint]]; [self addConstraints: @[_contentOverlayLeftConstraint, _contentOverlayTopConstraint]]; _constraintsAreAdded = YES; } [super updateConstraints]; } 

Cortacircuitos de creación de restricción

Encuentro el método NSLayoutConstraint para crear restricciones individuales bastante largas y poco claras. Agregué una categoría para facilitar esto y proporcionar descripciones simbólicas de los types específicos de restricción que se están creando. Voy a agregar más methods a él, ya que parecen útiles, pero esto es lo que tengo hasta ahora:

UIView + Constraints.h

 @interface UIView (Constraints) -(NSLayoutConstraint*) widthConstraintWithConstant: (CGFloat) initialWidth; -(NSLayoutConstraint*) heightConstraintWithConstant: (CGFloat) initialHeight; -(NSLayoutConstraint*) leftOffsetFromView: (UIView*) view withConstant: (CGFloat) offset; -(NSLayoutConstraint*) topOffsetFromView: (UIView*) view withConstant: (CGFloat) offset; @end 

UIView + Constraints.m

 @implementation UIView (Constraints) -(NSLayoutConstraint*) widthConstraintWithConstant: (CGFloat) initial { return [NSLayoutConstraint constraintWithItem: self attribute: NSLayoutAttributeWidth relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 1.0 constant: initial]; } -(NSLayoutConstraint*) heightConstraintWithConstant: (CGFloat) initial { return [NSLayoutConstraint constraintWithItem: self attribute: NSLayoutAttributeHeight relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 1.0 constant: initial]; } -(NSLayoutConstraint*) leftOffsetFromView: (UIView*) view withConstant: (CGFloat) offset { return [NSLayoutConstraint constraintWithItem: self attribute: NSLayoutAttributeLeft relatedBy: NSLayoutRelationEqual toItem: view attribute: NSLayoutAttributeLeft multiplier: 1.0 constant: offset]; } -(NSLayoutConstraint*) topOffsetFromView: (UIView*) view withConstant: (CGFloat) offset { return [NSLayoutConstraint constraintWithItem: self attribute: NSLayoutAttributeTop relatedBy: NSLayoutRelationEqual toItem: view attribute: NSLayoutAttributeTop multiplier: 1.0 constant: offset]; } @end