Emulación del comportamiento de ajuste de aspecto mediante restricciones de AutoLayout en Xcode 6

Quiero utilizar AutoLayout para dimensionar y distribuir una vista de manera que recuerde el modo de contenido de ajuste de aspecto de UIImageView.

Tengo una subvista dentro de una vista de contenedor en Interface Builder. La subvista tiene una relación de aspecto inherente que deseo respetar. El tamaño de la vista del contenedor es desconocido hasta el time de ejecución.

Si la relación de aspecto de la vista del contenedor es más ancha que la subvista, entonces quiero que la altura de la subvista sea igual a la altura de la vista principal.

Si la relación de aspecto de la vista del contenedor es más alta que la subview, entonces quiero que el ancho de la subview sea igual al ancho de la vista principal.

En cualquier caso, deseo que la subvista se centre horizontal y verticalmente en la vista del contenedor.

¿Hay alguna manera de lograr esto usando restricciones de AutoLayout en Xcode 6 o en la versión anterior? Lo ideal sería utilizar Interface Builder, pero si no, quizás sea posible definir dichas restricciones mediante progtwigción.

No estás describiendo escala-a-ajuste; estás describiendo el aspecto apropiado. (He editado tu pregunta a este respecto). La subvista se vuelve lo más grande posible mientras mantiene su relación de aspecto y se ajusta completamente dentro de su elemento primario.

De todos modos, puedes hacer esto con layout automático. Puedes hacerlo completamente en IB a partir de Xcode 5.1. Comencemos con algunas vistas:

algunas vistas

La vista verde claro tiene una relación de aspecto de 4: 1. La vista de color verde oscuro tiene una relación de aspecto de 1: 4. Voy a configurar restricciones para que la vista azul ocupe la mitad superior de la pantalla, la vista de color rosa ocupe la mitad inferior de la pantalla y cada vista de color verde se expande lo más posible mientras mantiene su relación de aspecto y ajuste en su envase.

Primero, crearé restricciones en los cuatro lados de la vista azul. Lo fijaré a su vecino más cercano en cada borde, con una distancia de 0. Me aseguro de apagar los márgenes:

limitaciones azules

Tenga en count que no actualizo el marco todavía. Me resulta más fácil dejar espacio entre las vistas al configurar las restricciones, y simplemente establecer las constantes en 0 (o lo que sea) a mano.

A continuación, pincho los bordes izquierdo, inferior y derecho de la vista rosa a su vecino más cercano. No necesito configurar una restricción de borde superior porque su borde superior ya está restringido al borde inferior de la vista azul.

restricciones rosadas

También necesito una restricción de altura igual entre las vistas rosa y azul. Esto hará que cada uno llene la mitad de la pantalla:

introduzca la descripción de la imagen aquí

Si le digo a Xcode que actualice todos los cuadros ahora, obtendré esto:

contenedores dispuestos

Entonces, las restricciones que he establecido hasta ahora son correctas. Deshago eso y comienzo a trabajar en la vista verde claro.

La apariencia de la vista verde claro requiere cinco restricciones:

  • Una restricción de relación de aspecto de prioridad requerida en la vista verde claro. Puede crear esta restricción en un xib o storyboard con Xcode 5.1 o posterior.
  • Una restricción de prioridad requerida que limita el ancho de la vista verde claro para que sea menor o igual que el ancho de su contenedor.
  • Una restricción de alta prioridad que establece el ancho de la vista verde claro para que sea igual al ancho de su contenedor.
  • Una restricción de prioridad requerida que limita la altura de la vista verde claro para que sea menor o igual a la altura de su contenedor.
  • Una restricción de alta prioridad que establece la altura de la vista verde claro para que sea igual a la altura de su contenedor.

Consideremos las dos restricciones de ancho. La restricción menor o igual, por sí misma, no es suficiente para determinar el ancho de la vista verde claro; muchos anchos se ajustarán a la restricción. Dado que hay ambigüedad, la reproducción automática intentará elegir una solución que minimice el error en la otra restricción (alta prioridad, pero no requerida). Minimizar el error significa hacer el ancho lo más cercano posible al ancho del contenedor, sin violar la restricción requerida menor o igual.

Lo mismo sucede con la restricción de altura. Y dado que también se requiere la restricción de relación de aspecto, solo puede maximizar el tamaño de la subvista a lo largo de un eje (a less que el contenedor tenga la misma relación de aspecto que la subvista).

Así que primero creo la restricción de la relación de aspecto:

aspecto superior

Luego creo restricciones de ancho y altura iguales con el contenedor:

tamaño igual

Necesito editar estas restricciones para que sean restricciones menores o iguales:

arriba menor o igual tamaño

A continuación, necesito crear otro set de restricciones de ancho y altura iguales con el contenedor:

tamaño igual de nuevo

Y tengo que hacer que estas nuevas restricciones sean menores que las requeridas:

igual superior no se requiere

Finalmente, solicitó que la subvista se centre en su contenedor, así que configuraré esas restricciones:

arriba centrado

Ahora, para probar, seleccionaré el controller de vista y pediré a Xcode que actualice todos los cuadros. Esto es lo que obtengo:

diseño superior incorrecto

¡Vaya! La subvista se ha expandido para llenar completamente su contenedor. Si lo selecciono, puedo ver que, de hecho, mantiene su relación de aspecto, pero está haciendo un aspecto: relleno en lugar de un ajuste de aspecto.

El problema es que en una restricción menor o igual, importa qué vista está en cada extremo de la restricción, y Xcode ha configurado la restricción opuesta a mi expectativa. Podría seleccionar cada una de las dos restricciones e invertir sus elementos primero y segundo. En cambio, solo seleccionaré la subvista y cambiaré las restricciones para ser mayor o igual:

arreglar las principales limitaciones

Xcode actualiza el layout:

diseño superior correcto

Ahora hago todas las mismas cosas a la vista verde oscuro en la parte inferior. Necesito asegurarme de que su relación de aspecto sea 1: 4 (Xcode lo networkingimensionó de una manera extraña ya que no tenía restricciones). No volveré a mostrar los pasos ya que son iguales. Este es el resultado:

corrección de diseño superior e inferior

Ahora puedo ejecutarlo en el simulador de iPhone 4S, que tiene un tamaño de pantalla diferente al utilizado por IB, y la rotation de testing:

prueba de iphone 4s

Y puedo probar en el simulador de iPhone 6:

prueba iphone 6

Subí mi storyboard final a esta esencia para tu comodidad.

Rob, tu respuesta es genial! También sé que esta pregunta trata específicamente de lograr esto mediante el uso de layout automático. Sin embargo, solo como reference, me gustaría mostrar cómo se puede hacer esto en el código. Configuró las vistas superior e inferior (azul y rosa) como mostró Rob. Luego creas un AspectFitView personalizado:

AspectFitView.h :

 #import <UIKit/UIKit.h> @interface AspectFitView : UIView @property (nonatomic, strong) UIView *childView; @end 

AspectFitView.m :

 #import "AspectFitView.h" @implementation AspectFitView - (void)setChildView:(UIView *)childView { if (_childView) { [_childView removeFromSuperview]; } _childView = childView; [self addSubview:childView]; [self setNeedsLayout]; } - (void)layoutSubviews { [super layoutSubviews]; if (_childView) { CGSize childSize = _childView.frame.size; CGSize parentSize = self.frame.size; CGFloat aspectRatioForHeight = childSize.width / childSize.height; CGFloat aspectRatioForWidth = childSize.height / childSize.width; if ((parentSize.height * aspectRatioForHeight) > parentSize.height) { // whole height, adjust width CGFloat width = parentSize.width * aspectRatioForWidth; _childView.frame = CGRectMake((parentSize.width - width) / 2.0, 0, width, parentSize.height); } else { // whole width, adjust height CGFloat height = parentSize.height * aspectRatioForHeight; _childView.frame = CGRectMake(0, (parentSize.height - height) / 2.0, parentSize.width, height); } } } @end 

Luego, cambia la class de las vistas azules y rosas en el guión gráfico para que sea AspectFitView . Finalmente, establece dos puntos de venta en su viewcontroller topAspectFitView y bottomAspectFitView y establece sus childView s en viewDidLoad :

 - (void)viewDidLoad { [super viewDidLoad]; UIView *top = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 100)]; top.backgroundColor = [UIColor lightGrayColor]; UIView *bottom = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 500)]; bottom.backgroundColor = [UIColor greenColor]; _topAspectFitView.childView = top; _bottomAspectFitView.childView = bottom; } 

Por lo tanto, no es difícil hacerlo en código y sigue siendo muy adaptable y funciona con vistas de diferentes tamaños y diferentes relaciones de aspecto.

Actualización de julio de 2015 : encuentre una aplicación de demostración aquí: https://github.com/jfahrenkrug/SPWKAspectFitView

Necesitaba una solución de la respuesta aceptada, pero ejecutada desde el código. La forma más elegante que he encontrado es usar el marco de Masonry .

 #import "Masonry.h" ... [view mas_makeConstraints:^(MASConstraintMaker *make) { make.width.equalTo(view.mas_height).multipliedBy(aspectRatio); make.size.lessThanOrEqualTo(superview); make.size.equalTo(superview).with.priorityHigh(); make.center.equalTo(superview); }]; 

Me encontré deseando un comportamiento de relleno de aspecto para que una UIImageView mantuviera su propia relación de aspecto y llenara por completo la vista de contenedor. Confusamente, mi UIImageView estaba rompiendo AMBOS anchos de alta prioridad y restricciones de altura igual (descritos en la respuesta de Rob) y renderizado a resolución completa.

La solución era simplemente establecer la prioridad de resistencia de compression de contenido de UIImageView más baja que la prioridad de las restricciones de ancho igual e igual altura:

Resistencia a la compresión de contenido

Esto es para macOS.

Tengo problemas para usar la forma de Rob para lograr un ajuste de aspecto en la aplicación OS X. Pero lo hice de otra manera: en lugar de usar el ancho y la altura, usé el espacio principal, posterior, superior e inferior.

Básicamente, agregue dos espacios principales donde uno sea> = 0 @ 1000 prioridad requerida y otro sea = 0 @ 250 prioridad baja. Realice las mismas configuraciones en el espacio posterior, superior e inferior.

Por supuesto, debe establecer la relación de aspecto y centrar X y centrar Y.

¡Y luego el trabajo está listo!

introduzca la descripción de la imagen aquí introduzca la descripción de la imagen aquí

Esta es una excelente respuesta del puerto de @ rob_mayoff a un enfoque centrado en el código, utilizando objects NSLayoutAnchor y portado a Xamarin. Para mí, NSLayoutAnchor y las classs relacionadas han hecho que AutoLayout sea mucho más fácil de progtwigr:

 public class ContentView : UIView { public ContentView (UIColor fillColor) { BackgroundColor = fillColor; } } public class MyController : UIViewController { public override void ViewDidLoad () { base.ViewDidLoad (); //Starting point: var view = new ContentView (UIColor.White); blueView = new ContentView (UIColor.FromRGB (166, 200, 255)); view.AddSubview (blueView); lightGreenView = new ContentView (UIColor.FromRGB (200, 255, 220)); lightGreenView.Frame = new CGRect (20, 40, 200, 60); view.AddSubview (lightGreenView); pinkView = new ContentView (UIColor.FromRGB (255, 204, 240)); view.AddSubview (pinkView); greenView = new ContentView (UIColor.Green); greenView.Frame = new CGRect (80, 20, 40, 200); pinkView.AddSubview (greenView); //Now start doing in code the things that @rob_mayoff did in IB //Make the blue view size up to its parent, but half the height blueView.TranslatesAutoresizingMaskIntoConstraints = false; var blueConstraints = new [] { blueView.LeadingAnchor.ConstraintEqualTo(view.LayoutMarginsGuide.LeadingAnchor), blueView.TrailingAnchor.ConstraintEqualTo(view.LayoutMarginsGuide.TrailingAnchor), blueView.TopAnchor.ConstraintEqualTo(view.LayoutMarginsGuide.TopAnchor), blueView.HeightAnchor.ConstraintEqualTo(view.LayoutMarginsGuide.HeightAnchor, (nfloat) 0.5) }; NSLayoutConstraint.ActivateConstraints (blueConstraints); //Make the pink view same size as blue view, and linked to bottom of blue view pinkView.TranslatesAutoresizingMaskIntoConstraints = false; var pinkConstraints = new [] { pinkView.LeadingAnchor.ConstraintEqualTo(blueView.LeadingAnchor), pinkView.TrailingAnchor.ConstraintEqualTo(blueView.TrailingAnchor), pinkView.HeightAnchor.ConstraintEqualTo(blueView.HeightAnchor), pinkView.TopAnchor.ConstraintEqualTo(blueView.BottomAnchor) }; NSLayoutConstraint.ActivateConstraints (pinkConstraints); //From here, address the aspect-fitting challenge: lightGreenView.TranslatesAutoresizingMaskIntoConstraints = false; //These are the must-fulfill constraints: var lightGreenConstraints = new [] { //Aspect ratio of 1 : 5 NSLayoutConstraint.Create(lightGreenView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, lightGreenView, NSLayoutAttribute.Width, (nfloat) 0.20, 0), //Cannot be larger than parent's width or height lightGreenView.WidthAnchor.ConstraintLessThanOrEqualTo(blueView.WidthAnchor), lightGreenView.HeightAnchor.ConstraintLessThanOrEqualTo(blueView.HeightAnchor), //Center in parent lightGreenView.CenterYAnchor.ConstraintEqualTo(blueView.CenterYAnchor), lightGreenView.CenterXAnchor.ConstraintEqualTo(blueView.CenterXAnchor) }; //Must-fulfill foreach (var c in lightGreenConstraints) { c.Priority = 1000; } NSLayoutConstraint.ActivateConstraints (lightGreenConstraints); //Low priority constraint to attempt to fill parent as much as possible (but lower priority than previous) var lightGreenLowPriorityConstraints = new [] { lightGreenView.WidthAnchor.ConstraintEqualTo(blueView.WidthAnchor), lightGreenView.HeightAnchor.ConstraintEqualTo(blueView.HeightAnchor) }; //Lower priority foreach (var c in lightGreenLowPriorityConstraints) { c.Priority = 750; } NSLayoutConstraint.ActivateConstraints (lightGreenLowPriorityConstraints); //Aspect-fit on the green view now greenView.TranslatesAutoresizingMaskIntoConstraints = false; var greenConstraints = new [] { //Aspect ratio of 5:1 NSLayoutConstraint.Create(greenView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, greenView, NSLayoutAttribute.Width, (nfloat) 5.0, 0), //Cannot be larger than parent's width or height greenView.WidthAnchor.ConstraintLessThanOrEqualTo(pinkView.WidthAnchor), greenView.HeightAnchor.ConstraintLessThanOrEqualTo(pinkView.HeightAnchor), //Center in parent greenView.CenterXAnchor.ConstraintEqualTo(pinkView.CenterXAnchor), greenView.CenterYAnchor.ConstraintEqualTo(pinkView.CenterYAnchor) }; //Must fulfill foreach (var c in greenConstraints) { c.Priority = 1000; } NSLayoutConstraint.ActivateConstraints (greenConstraints); //Low priority constraint to attempt to fill parent as much as possible (but lower priority than previous) var greenLowPriorityConstraints = new [] { greenView.WidthAnchor.ConstraintEqualTo(pinkView.WidthAnchor), greenView.HeightAnchor.ConstraintEqualTo(pinkView.HeightAnchor) }; //Lower-priority than above foreach (var c in greenLowPriorityConstraints) { c.Priority = 750; } NSLayoutConstraint.ActivateConstraints (greenLowPriorityConstraints); this.View = view; view.LayoutIfNeeded (); } } 

Tal vez esta sea la respuesta más corta, con la Masonería .

 [containerView addSubview:subview]; [subview mas_makeConstraints:^(MASConstraintMaker *make) { if (contentMode == ContentMode_scaleToFill) { make.edges.equalTo(containerView); } else { make.center.equalTo(containerView); make.edges.equalTo(containerView).priorityHigh(); make.width.equalTo(content.mas_height).multipliedBy(4.0 / 3); if (contentMode == ContentMode_scaleAspectFit) { make.width.height.lessThanOrEqualTo(containerView); } else { // contentMode == ContentMode_scaleAspectFill make.width.height.greaterThanOrEqualTo(containerView); } } }];