ios - developer - Emulación de comportamiento de ajuste de aspecto utilizando restricciones de AutoLayout en Xcode 6
programmatically constraints swift 4 (7)
¡Rob, tu respuesta es asombrosa!
También sé que esta pregunta se trata específicamente de lograr esto mediante el diseño automático.
Sin embargo, solo como referencia, me gustaría mostrar cómo se puede hacer esto en código.
Configura las vistas superior e inferior (azul y rosa) tal como mostró Rob.
Luego, crea 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
A continuación, cambia la clase de las vistas azul y rosa en el guión gráfico para que sean
AspectFitView
s.
Finalmente, configura dos salidas en su controlador de vista
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 hacer esto en código y todavía es muy adaptable y funciona con vistas de tamaño variable y diferentes relaciones de aspecto.
Actualización de julio de 2015 : encuentre una aplicación de demostración aquí: https://github.com/jfahrenkrug/SPWKAspectFitView
Quiero usar AutoLayout para dimensionar y diseñar una vista de una manera que recuerde el modo de contenido de aspecto adaptado 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 tiempo de ejecución.
Si la relación de aspecto de la vista del contenedor es más amplia 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 subvista, entonces quiero que el ancho de la subvista sea igual al ancho de la vista principal.
En cualquier caso, deseo que la subvista esté centrada horizontal y verticalmente dentro de la vista del contenedor.
¿Hay alguna manera de lograr esto usando las restricciones de AutoLayout en Xcode 6 o en la versión anterior? Lo ideal es usar Interface Builder, pero si no es posible, es posible definir tales restricciones mediante programación.
Este es un puerto de la excelente respuesta de @ rob_mayoff a un enfoque centrado en el código, utilizando objetos
NSLayoutAnchor
y portado a Xamarin.
Para mí,
NSLayoutAnchor
y las clases relacionadas han hecho que AutoLayout sea mucho más fácil de programar:
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 ();
}
}
Me encontré queriendo un comportamiento de relleno de aspecto para que un UIImageView mantuviera su propia relación de aspecto y llenara completamente la vista del contenedor. Confusamente, mi UIImageView estaba rompiendo AMBAS restricciones de igual ancho y altura igual de alta prioridad (descritas en la respuesta de Rob) y renderizando a resolución completa.
La solución fue simplemente establecer la prioridad de resistencia de compresión de contenido de UIImageView por debajo de la prioridad de las restricciones de igual ancho y altura:
Necesitaba una solución de la respuesta aceptada, pero ejecutada desde el código. La forma más elegante que he encontrado es usando 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);
}];
No estás describiendo escala a medida; Estás describiendo un ajuste de aspecto. (He editado su pregunta a este respecto.) La subvista se vuelve tan grande como sea posible mientras mantiene su relación de aspecto y se ajusta completamente dentro de su padre.
De todos modos, puedes hacer esto con el diseño automático. Puede hacerlo completamente en IB a partir de Xcode 5.1. Comencemos con algunas vistas:
La vista verde claro tiene una relación de aspecto de 4: 1. La vista verde oscuro tiene una relación de aspecto de 1: 4. Voy a establecer restricciones para que la vista azul llene la mitad superior de la pantalla, la vista rosa llene la mitad inferior de la pantalla y cada vista verde se expanda tanto como sea posible, manteniendo 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 desactivar los márgenes:
Tenga en cuenta que todavía no actualizo el marco. Me resulta más fácil dejar espacio entre las vistas al configurar restricciones, y simplemente establecer las constantes a 0 (o lo que sea) a mano.
A continuación, fijo 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.
También necesito una restricción de igual altura entre las vistas rosa y azul. Esto hará que cada uno llene la mitad de la pantalla:
Si le digo a Xcode que actualice todos los cuadros ahora, obtengo esto:
Entonces, las restricciones que he establecido hasta ahora son correctas. Lo deshago y empiezo a trabajar en la vista verde claro.
El ajuste de aspecto 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 que la altura de su contenedor.
- Una restricción de alta prioridad que establece que la altura de la vista verde clara sea igual a la altura de su contenedor.
Consideremos las dos restricciones de ancho. La restricción menor o igual, por sí sola, no es suficiente para determinar el ancho de la vista verde claro; muchos anchos se ajustarán a la restricción. Dado que existe ambigüedad, la distribución automática intentará elegir una solución que minimice el error en la otra restricción (de alta prioridad pero no obligatoria). Minimizar el error significa hacer que el ancho sea lo más cercano posible al ancho del contenedor, sin violar la restricción menor o igual requerida.
Lo mismo sucede con la restricción de altura. Y dado que también se requiere la restricción de la relación de aspecto, solo puede maximizar el tamaño de la subvista a lo largo de un eje (a menos que el contenedor tenga la misma relación de aspecto que la subvista).
Entonces, primero creo la restricción de la relación de aspecto:
Luego creo restricciones de ancho y altura iguales con el contenedor:
Necesito editar estas restricciones para que sean menores o iguales:
A continuación, necesito crear otro conjunto de restricciones de ancho y altura iguales con el contenedor:
Y necesito hacer que estas nuevas restricciones tengan menos prioridad de la requerida:
Finalmente, solicitó que la subvista se centre en su contenedor, por lo que estableceré esas restricciones:
Ahora, para probar, seleccionaré el controlador de vista y le pediré a Xcode que actualice todos los cuadros. Esto es lo que obtengo:
¡Uy! La subvista se ha expandido para llenar completamente su contenedor. Si lo selecciono, puedo ver que, de hecho, ha mantenido su relación de aspecto, pero está haciendo un relleno de aspecto en lugar de un ajuste de aspecto.
El problema es que en una restricción menor o igual, importa qué vista se encuentre en cada extremo de la restricción, y Xcode ha establecido la restricción opuesta a mi expectativa. Podría seleccionar cada una de las dos restricciones e invertir su primer y segundo elemento. En cambio, solo seleccionaré la subvista y cambiaré las restricciones para que sean mayores o iguales:
Xcode actualiza el diseño:
Ahora hago lo mismo con la vista verde oscuro en la parte inferior. Necesito asegurarme de que su relación de aspecto sea 1: 4 (Xcode lo redimensionó de una manera extraña ya que no tenía restricciones). No volveré a mostrar los pasos ya que son los mismos. Aquí está el resultado:
Ahora puedo ejecutarlo en el simulador de iPhone 4S, que tiene un tamaño de pantalla diferente al utilizado por IB, y probar la rotación:
Y puedo probar en el simulador de iPhone 6:
He subido mi guión gráfico final a esta esencia para su conveniencia.
Tal vez esta sea la respuesta más corta, con Masonry , que también admite relleno de aspecto y estiramiento.
typedef NS_ENUM(NSInteger, ContentMode) {
ContentMode_aspectFit,
ContentMode_aspectFill,
ContentMode_stretch
}
// ....
[containerView addSubview:subview];
[subview mas_makeConstraints:^(MASConstraintMaker *make) {
if (contentMode == ContentMode_stretch) {
make.edges.equalTo(containerView);
}
else {
make.center.equalTo(containerView);
make.edges.equalTo(containerView).priorityHigh();
make.width.equalTo(content.mas_height).multipliedBy(4.0 / 3); // the aspect ratio
if (contentMode == ContentMode_aspectFit) {
make.width.height.lessThanOrEqualTo(containerView);
}
else { // contentMode == ContentMode_aspectFill
make.width.height.greaterThanOrEqualTo(containerView);
}
}
}];
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, utilicé el espacio inicial, posterior, superior e inferior.
Básicamente, agregue dos espacios iniciales donde uno es> = 0 @ 1000 prioridad requerida y otro es = 0 @ 250 prioridad baja. Realice la misma configuración en el espacio posterior, superior e inferior.
Por supuesto, debe establecer la relación de aspecto y el centro X y el centro Y.
¡Y entonces el trabajo está hecho!