Emulowanie zachowania dopasowania aspektu przy użyciu ograniczeń AutoLayout w Xcode 6
Chcę użyć AutoLayout do rozmiaru i układu widoku w sposób przypominający tryb zawartości interfejsu UIImageView.
Mam subview wewnątrz widoku kontenera w Interface Builder. Subview ma pewne nieodłączne proporcje, które chciałbym szanować. Rozmiar widoku kontenera jest Nieznany do czasu uruchomienia.
Jeśli proporcje widoku kontenera są szersze niż widok podrzędny, to chcę, aby wysokość widoku podrzędnego była równa wysokości widoku nadrzędnego.
Jeśli pojemnik proporcje widoku są wyższe niż podview, a następnie chcę, aby szerokość podview była równa szerokości widoku nadrzędnego.
W obu przypadkach chciałbym, aby subview był wyśrodkowany poziomo i pionowo w widoku kontenera.
Czy istnieje sposób, aby to osiągnąć przy użyciu ograniczeń AutoLayout w Xcode 6 lub w poprzedniej wersji? Najlepiej używać kreatora interfejsów, ale jeśli nie, możliwe jest programowe definiowanie takich ograniczeń.
7 answers
Nie opisujesz skali do dopasowania; opisujesz aspekt dopasowania. (Zredagowałem twoje pytanie w tym zakresie.) Subview staje się tak duży, jak to możliwe, zachowując swój współczynnik proporcji i dopasowując się całkowicie wewnątrz jego rodzica.
W każdym razie, można to zrobić z auto layout. Możesz to zrobić całkowicie w IB od Xcode 5.1. Zacznijmy od kilku widoków:
Jasnozielony Widok ma proporcje 4: 1. Ciemnozielony Widok ma proporcje 1: 4. I ' m Ustaw ograniczenia tak, aby Niebieski widok wypełniał górną połowę ekranu, różowy widok wypełniał dolną połowę ekranu, a każdy zielony widok rozszerzał się w miarę możliwości, zachowując proporcje i dopasowując się do kontenera.
Najpierw stworzę ograniczenia na wszystkich czterech stronach niebieskiego widoku. Przypiszę go do najbliższego sąsiada na każdej krawędzi, z odległości 0. Upewniam się, że wyłączam marginesy:
Zauważ, że ja nie aktualizuję rama jeszcze. Łatwiej jest mi zostawić miejsce między widokami podczas ustawiania ograniczeń i po prostu ustawić stałe na 0 (lub cokolwiek) ręcznie.
Następnie przypinam lewą, dolną i prawą krawędź różowego widoku do najbliższego sąsiada. Nie muszę ustawiać ograniczenia górnej krawędzi, ponieważ jego górna krawędź jest już ograniczona do dolnej krawędzi niebieskiego widoku.
Potrzebuję też ograniczenia wysokości między różowym i niebieskim widokiem. To ich uczyni każde wypełnienie połowy ekranu:
Jeśli powiem Xcode, aby teraz zaktualizował wszystkie ramki, dostaję to:
Więc ograniczenia, które do tej pory ustawiłem są poprawne. Cofam to i zaczynam pracę nad jasnozielonym widokiem.
Dopasowanie widoku jasnozielonego wymaga pięciu ograniczeń:
- ograniczenie proporcji o priorytecie wymaganym w widoku jasnozielonym. Możesz utworzyć to ograniczenie w xib lub storyboard z Xcode 5.1 lub później.
- ograniczenie o wymaganym priorytecie ograniczające szerokość jasnozielonego widoku, aby była mniejsza lub równa szerokości kontenera.
- ograniczenie o wysokim priorytecie ustawiające szerokość jasnozielonego widoku równą szerokości kontenera.
- ograniczenie o wymaganym priorytecie ograniczające wysokość jasnozielonego widoku, aby była mniejsza lub równa wysokości jego kontenera.
- ograniczenie o wysokim priorytecie ustawiające wysokość jasnozielonego widoku jest równa wysokości jego pojemnika.
Rozważmy dwa ograniczenia szerokości. Ograniczenie mniejsze lub równe samo w sobie nie jest wystarczające do określenia szerokości jasnozielonego widoku; wiele szerokości będzie pasować do ograniczenia. Ponieważ istnieje niejednoznaczność, autolayout spróbuje wybrać rozwiązanie, które minimalizuje błąd w drugim ograniczeniu (o wysokim priorytecie, ale nie jest wymagane). Minimalizacja Błędu oznacza, że szerokość jest jak najbardziej zbliżona do szerokości kontenera, nie naruszając jednocześnie wymaganego ograniczenia.
To samo dzieje się z ograniczeniem wysokości. A ponieważ wymagane jest również ograniczenie proporcji, może ono tylko zmaksymalizować rozmiar subview wzdłuż jednej osi (chyba że kontener ma taki sam współczynnik proporcji jak subview).
Więc najpierw tworzę ograniczenie proporcji:
Następnie tworzę równe ograniczenia szerokości i wysokości z "kontener": {]}
Muszę edytować te ograniczenia, aby były mniej niż równe:
Następnie muszę utworzyć kolejny zestaw o jednakowej szerokości i wysokości z kontenerem:
I muszę uczynić te nowe ograniczenia mniej niż wymagany Priorytet:
Na koniec poprosiłeś o wyśrodkowanie subview w kontenerze, więc skonfiguruję te ograniczenia:
Teraz, aby test, wybieram kontroler widoku i poprosić Xcode, aby zaktualizować wszystkie ramki. Oto co dostaję:
Oops! Subview rozszerzył się, aby całkowicie wypełnić swój kontener. Jeśli go zaznaczę, widzę, że w rzeczywistości zachowuje swój współczynnik proporcji, ale robi aspect - fill zamiast aspect- fit .
Problem polega na tym, że na mniejszym lub równym ograniczeniu ważne jest, który Widok znajduje się na każdym końcu ograniczenia, a Xcode ustawił ograniczenie odwrotne od moich oczekiwań. Mogłem wybrać każdy z dwóch ograniczeń i odwrócić jego pierwszą i drugą pozycję. Zamiast tego po prostu zaznaczę subview i zmienię ograniczenia na większe lub równe:
Xcode aktualizuje układ:
Teraz robię to samo z ciemnozielonym widokiem na dole. Muszę się upewnić, że jego współczynnik proporcji jest 1: 4 (Xcode zmienił rozmiar w dziwny sposób, ponieważ nie ma ograniczeń). I won ' t show znowu kroki, bo są takie same. Oto wynik:
Teraz mogę go uruchomić w symulatorze iPhone 4S, który ma inny rozmiar ekranu niż używany IB, i test rotacji:
A ja mogę przetestować w symulatorze iPhone 6:
Załadowałem mój ostatni storyboard do ten gist dla Twojej wygody.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-26 18:39:01
Rob, Twoja odpowiedź jest niesamowita!
Wiem też, że to pytanie dotyczy w szczególności osiągnięcia tego za pomocą auto-layout. Jednak jako odniesienie chciałbym pokazać, jak można to zrobić w kodzie. Ustawiasz górne i dolne widoki (niebieskie i różowe), tak jak pokazywał Rob. Następnie tworzysz własny 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
Następnie zmieniasz klasę niebieskich i różowych widoków w storyboardzie na AspectFitView
s. na koniec ustawiasz dwa wyjścia do swojego viewcontrollera topAspectFitView
i bottomAspectFitView
i ustawiasz ich childView
s W 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;
}
Więc nie jest trudno to zrobić w kodzie i nadal jest bardzo elastyczny i działa z różnymi widokami i różnymi proporcjami.
Aktualizacja lipiec 2015 : Znajdź aplikację demo tutaj: https://github.com/jfahrenkrug/SPWKAspectFitView
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-17 13:06:04
Potrzebowałem rozwiązania z zaakceptowanej odpowiedzi, ale wykonanego z kodu. Najbardziej eleganckim sposobem, jaki znalazłem, jest użycie ramy Murowanej .
#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);
}];
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-14 13:05:39
To jest dla macOS.
Mam problem z wykorzystaniem sposobu Roba, aby uzyskać dopasowanie aspect fit w aplikacji OS X. Ale zrobiłem to w inny sposób-zamiast używać szerokości i wysokości, użyłem wiodącej, ciągłej, górnej i dolnej przestrzeni.
Zasadniczo, dodaj dwie wiodące spacje, gdzie jedna to > = 0 @ 1000 wymagany priorytet, a druga to = 0 @250 niski priorytet. Wykonaj te same ustawienia dla spacji końcowej, górnej i dolnej.
Oczywiście trzeba ustawić proporcje i środek X i środek Y.
A potem robota skończona!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-17 09:51:31
Uznałem, że chcę zachowania aspect - fill , aby interfejs UIImageView zachowywał swój własny współczynnik proporcji i całkowicie wypełniał widok kontenera. Co ciekawe, mój interfejs użytkownika łamał zarówno ograniczenia o wysokim priorytecie o równej szerokości, jak i równej wysokości (opisane w odpowiedzi Roba) oraz renderowanie w pełnej rozdzielczości.
Rozwiązaniem było po prostu ustawienie priorytetu odporności na kompresję zawartości UIImageView poniżej priorytetu równej szerokości i równej wysokości ograniczenia:
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-31 03:37:23
Jest to port doskonałej odpowiedzi @rob_mayoff na podejście zorientowane na kod, wykorzystujące obiekty NSLayoutAnchor
i przeniesione do Xamarin. Dla mnie, NSLayoutAnchor
i pokrewne klasy znacznie ułatwiły AutoLayout:
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 ();
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-20 00:00:36
Być może jest to najkrótsza odpowiedź, z murem .
[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);
}
}
}];
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-12 12:33:17