Je souhaite utiliser AutoLayout pour dimensionner et mettre en forme une vue d'une manière qui rappelle le mode de contenu adapté à UIImageView.
J'ai une sous-vue dans une vue de conteneur dans Interface Builder. La sous-vue a un certain rapport d'aspect inhérent que je souhaite respecter. La taille de la vue conteneur est inconnue jusqu'au moment de l'exécution.
Si les proportions de la vue conteneur sont plus larges que la sous-vue, je souhaite que la hauteur de la sous-vue soit égale à celle de la vue parente.
Si les proportions de la vue conteneur sont plus hautes que celles de la sous-vue, je souhaite que la largeur de la sous-vue soit égale à celle de la vue parente.
Dans les deux cas, je souhaite que la sous-vue soit centrée horizontalement et verticalement dans la vue du conteneur.
Existe-t-il un moyen d'y parvenir en utilisant les contraintes AutoLayout dans Xcode 6 ou dans la version précédente? Idéalement, à l'aide d'Interface Builder, mais sinon, il est possible de définir de telles contraintes par programme.
Vous n'êtes pas en train de décrire l'échelle; vous décrivez l'aspect-fit. (J'ai modifié votre question à cet égard.) La sous-vue devient aussi grande que possible tout en conservant son rapport de format et en s'intégrant entièrement dans sa parent.
Quoi qu'il en soit, vous pouvez le faire avec la mise en page automatique. Vous pouvez le faire entièrement dans IB à partir de Xcode 5.1. Commençons par quelques vues:
La vue vert clair a un format d'image de 4: 1. La vue vert foncé a un format d'image de 1: 4. Je vais définir des contraintes pour que la vue bleue remplisse la moitié supérieure de l’écran, que la vue rose remplisse la moitié inférieure de l’écran et que chaque vue verte s’agrandisse autant que possible tout en conservant son rapport de format et d’adaptation au fond. récipient.
Tout d'abord, je vais créer des contraintes sur les quatre côtés de la vue bleue. Je l'épinglerai à son plus proche voisin de chaque bord, avec une distance de 0. Je m'assure de désactiver les marges:
Notez que je ne met pas encore le cadre à jour. Je trouve plus facile de laisser de la place entre les vues lors de la configuration des contraintes et de définir les constantes à 0 (ou autre) à la main.
Ensuite, j'attache les bords gauche, inférieur et droit de la vue rose à son voisin le plus proche. Je n'ai pas besoin de configurer une contrainte de bord supérieur car son bord supérieur est déjà contraint sur le bord inférieur de la vue bleue.
J'ai également besoin d'une contrainte d'égalité de hauteur entre les vues rose et bleue. Cela les fera remplir chacun la moitié de l'écran:
Si je dis à Xcode de mettre à jour tous les cadres maintenant, j'obtiens ceci:
Donc, les contraintes que j'ai mises en place jusqu'ici sont correctes. Je défait cela et commence à travailler sur la vue vert clair.
L'aspect adapté à la vue vert clair nécessite cinq contraintes:
Considérons les deux contraintes de largeur. La contrainte inférieure ou égale, en elle-même, ne suffit pas pour déterminer la largeur de la vue vert clair; de nombreuses largeurs conviendront à la contrainte. En raison de l'ambiguïté, autolayout essaiera de choisir une solution qui minimise l'erreur dans l'autre contrainte (priorité élevée mais non requise). Minimiser l'erreur signifie que la largeur doit être aussi proche que possible de la largeur du conteneur, sans pour autant enfreindre la contrainte inférieure ou égale requise.
La même chose se produit avec la contrainte de hauteur. Et comme la contrainte de format d'image est également requise, elle ne peut que maximiser la taille de la sous-vue le long d'un axe (à moins que le conteneur ait le même rapport de format que la sous-vue).
Donc, je crée d'abord la contrainte de format d'image:
Ensuite, je crée des contraintes égales de largeur et de hauteur avec le conteneur:
J'ai besoin de modifier ces contraintes pour qu'elles soient inférieures ou égales:
Ensuite, je dois créer un autre ensemble de contraintes de largeur et de hauteur identiques avec le conteneur:
Et je dois rendre ces nouvelles contraintes moins que la priorité requise:
Enfin, vous avez demandé que la sous-vue soit centrée dans son conteneur. Je vais donc définir ces contraintes:
Maintenant, pour tester, je vais sélectionner le contrôleur de vue et demander à Xcode de mettre à jour toutes les images. Voici ce que je reçois:
Oops! La sous-vue a été étendue pour remplir complètement son conteneur. Si je le sélectionne, je constate qu'en fait, il conserve son format d'image, mais il effectue un aspect - fill au lieu d'un aspect - fit .
Le problème est que, sur une contrainte inférieure ou égale, il importe de savoir quelle vue se trouve à chaque extrémité de la contrainte et Xcode a défini la contrainte opposée à celle que je prévoyais. Je pouvais sélectionner chacune des deux contraintes et inverser ses premier et deuxième éléments. Au lieu de cela, je vais simplement sélectionner la sous-vue et modifier les contraintes pour qu'elles soient supérieures ou égales:
Xcode met à jour la mise en page:
Maintenant, je fais les mêmes choses avec la vue vert foncé en bas. Je dois m'assurer que son format d'image est 1: 4 (Xcode l'a redimensionné de façon étrange car il n'avait pas de contraintes). Je ne montrerai plus les étapes car elles sont identiques. Voici le résultat:
Maintenant, je peux l'exécuter dans le simulateur iPhone 4S, dont la taille d'écran est différente de celle utilisée par IB, et tester la rotation:
Et je peux tester dans le simulateur iPhone 6:
J'ai téléchargé mon dernier storyboard sur this Gist pour votre commodité.
Rob, ta réponse est géniale! Je sais aussi que cette question concerne spécifiquement la réalisation de cet objectif en utilisant la mise en page automatique. Cependant, à titre de référence, je voudrais montrer comment cela peut être fait dans le code. Vous configurez les vues du haut et du bas (bleu et rose) exactement comme l'a montré Rob. Ensuite, vous créez un personnalisé AspectFitView
:
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
Ensuite, vous modifiez la classe des vues bleue et rose dans le scénario en AspectFitView
s. Enfin, vous définissez deux prises pour votre contrôleur de vue topAspectFitView
et bottomAspectFitView
et définissez leur childView
s dans 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;
}
Il n’est donc pas difficile de faire cela dans le code, mais il reste très adaptable et fonctionne avec des vues de tailles variables et des formats d’image différents.
Mise à jour juillet 2015 : Recherchez une application de démonstration ici: https://github.com/jfahrenkrug/SPWKAspectFitView
J'avais besoin d'une solution de la réponse acceptée, mais exécutée à partir du code. La manière la plus élégante que j'ai trouvée consiste à utiliser framework de maçonnerie .
#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);
}];
Ceci est pour macOS.
J'ai du mal à utiliser la méthode de Rob pour obtenir un bon ajustement sur l'application OS X. Mais je l'ai fait d'une autre manière: au lieu d'utiliser largeur et hauteur, j'ai utilisé les espaces de début, de fin, de haut et de bas.
En gros, ajoutez deux espaces de début où l'un est> = 0 @ 1000 priorité requise et un autre = = 0 @ 250 priorité faible. Faites les mêmes réglages pour les espaces de fin, haut et bas.
Bien sûr, vous devez définir les proportions, le centre X et le centre Y.
Et puis le travail est fait!
Je me suis retrouvé à vouloir un aspect -remplissage comportement afin qu'un UIImageView maintienne son propre rapport d'aspect et remplisse entièrement la vue du conteneur. De manière confuse, mon UIImageView supprimait les contraintes de priorité élevée et de largeur égale (décrites dans la réponse de Rob) de priorité élevée et rendait le rendu en pleine résolution.
La solution consistait simplement à définir une priorité de résistance à la compression du contenu de UIImageView inférieure à la priorité des contraintes d'égale largeur et d'égale hauteur:
Ceci est une excellente réponse de @ rob_mayoff à une approche centrée sur le code, en utilisant des objets NSLayoutAnchor
et portés sur Xamarin. Pour moi, NSLayoutAnchor
et les classes associées ont beaucoup simplifié la programmation de la mise en forme automatique:
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 ();
}
}
Peut-être que c'est la réponse la plus courte, avec Maçonnerie , qui prend également en charge le remplissage d'aspect et l'étirement.
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);
}
}
}];