使用Xcode 6中的AutoLayout约束模拟方面适合行为
本文翻译自:Emulating aspect-fit behaviour using AutoLayout constraints in Xcode 6
I want to use AutoLayout to size and layout a view in a manner that is reminiscent of UIImageView's aspect-fit content mode. 我想使用AutoLayout以一种让人联想到UIImageView的宽高比内容模式的方式来调整和布局视图。
I have a subview inside a container view in Interface Builder. 我在Interface Builder的容器视图中有一个子视图。 The subview has some inherent aspect ratio which I wish to respect. 子视图具有一些我希望尊重的固有宽高比。 The container view's size is unknown until runtime. 容器视图的大小在运行时之前是未知的。
If the container view's aspect ratio is wider than the subview, then I want the subview's height to equal the parent view's height. 如果容器视图的宽高比比子视图宽,那么我希望子视图的高度等于父视图的高度。
If the container view's aspect ratio is taller than the subview, then I want the subview's width to equal the parent view's width. 如果容器视图的宽高比高于子视图,那么我希望子视图的宽度等于父视图的宽度。
In either case I wish the subview to be centered horizontally and vertically within the container view. 在任何一种情况下,我都希望子视图在容器视图中水平和垂直居中。
Is there a way to achieve this using AutoLayout constraints in Xcode 6 or in previous version? 有没有办法在Xcode 6或以前的版本中使用AutoLayout约束来实现这一点? Ideally using Interface Builder, but if not perhaps it is possible to define such constraints programmatically. 理想情况下使用Interface Builder,但如果没有,也许可以通过编程方式定义这些约束。
#1楼
参考:https://stackoom.com/question/1k76h/使用Xcode-中的AutoLayout约束模拟方面适合行为
#2楼
You're not describing scale-to-fit; 你没有描述适合的尺度; you're describing aspect-fit. 你正在描述方面适合。 (I have edited your question in this regard.) The subview becomes as large as possible while maintaining its aspect ratio and fitting entirely inside its parent. (我已经在这方面编辑了你的问题。)子视图变得尽可能大,同时保持其纵横比并完全适合其父级。
Anyway, you can do this with auto layout. 无论如何,您可以使用自动布局执行此操作。 You can do it entirely in IB as of Xcode 5.1. 你可以在IB Xcode 5.1中完全完成它。 Let's start with some views: 让我们从一些观点开始:
The light green view has an aspect ratio of 4:1. 浅绿色视图的纵横比为4:1。 The dark green view has an aspect ratio of 1:4. 深绿色视图的纵横比为1:4。 I'm going to set up constraints so that the blue view fills the top half of the screen, the pink view fills the bottom half of the screen, and each green view expands as much as possible while maintaining its aspect ratio and fitting in its container. 我将设置约束,以便蓝色视图填充屏幕的上半部分,粉红色视图填充屏幕的下半部分,每个绿色视图尽可能地扩展,同时保持其纵横比并适合其容器。
First, I'll create constraints on all four sides of the blue view. 首先,我将在蓝色视图的所有四个边上创建约束。 I'll pin it to its nearest neighbor on each edge, with a distance of 0. I make sure to turn off margins: 我将它固定到每个边缘上最近的邻居,距离为0.我确保关闭边距:
Note that I don't update the frame yet. 请注意,我还没有更新框架。 I find it easier to leave room between the views when setting up constraints, and just set the constants to 0 (or whatever) by hand. 我发现在设置约束时在视图之间留出空间更容易,只需手动将常量设置为0(或其他)。
Next, I pin the left, bottom, and right edges of the pink view to its nearest neighbor. 接下来,我将粉红色视图的左,下,右边缘固定到最近的邻居。 I don't need to set up a top edge constraint because its top edge is already constrained to the bottom edge of the blue view. 我不需要设置上边缘约束,因为它的上边缘已经被约束到蓝色视图的下边缘。
I also need an equal-heights constraint between the pink and blue views. 我还需要粉红色和蓝色视图之间的等高限制。 This will make them each fill half the screen: 这将使他们每个填充屏幕的一半:
If I tell Xcode to update all the frames now, I get this: 如果我告诉Xcode现在更新所有帧,我得到这个:
So the constraints I've set up so far are correct. 所以我到目前为止设置的约束是正确的。 I undo that and start work on the light green view. 我将其撤消并开始处理浅绿色视图。
Aspect-fitting the light green view requires five constraints: 方面拟合浅绿色视图需要五个约束:
- A required-priority aspect ratio constraint on the light green view. 浅绿色视图上的必需优先宽高比约束。 You can create this constraint in a xib or storyboard with Xcode 5.1 or later. 您可以使用Xcode 5.1或更高版本在xib或storyboard中创建此约束。
- A required-priority constraint limiting the width of the light green view to be less than or equal to the width of its container. 限制浅绿色视图宽度的所需优先级约束小于或等于其容器的宽度。
- A high-priority constraint setting the width of the light green view to be equal to the width of its container. 高优先级约束,将浅绿色视图的宽度设置为等于其容器的宽度。
- A required-priority constraint limiting the height of the light green view to be less than or equal to the height of its container. 限制浅绿色视图高度的必需优先级约束小于或等于其容器的高度。
- A high-priority constraint setting the height of the light green view to be equal to the height of its container. 高优先级约束,将浅绿色视图的高度设置为等于其容器的高度。
Let's consider the two width constraints. 让我们考虑两个宽度约束。 The less-than-or-equal constraint, by itself, is not sufficient to determine the width of the light green view; 小于或等于约束本身不足以确定浅绿色视图的宽度; many widths will fit the constraint. 许多宽度将适合约束。 Since there's ambiguity, autolayout will try to choose a solution that minimizes the error in the other (high-priority but not required) constraint. 由于存在歧义,autolayout将尝试选择最小化另一个(高优先级但不是必需的)约束中的错误的解决方案。 Minimizing the error means making the width as close as possible to the container's width, while not violating the required less-than-or-equal constraint. 最小化错误意味着使宽度尽可能接近容器的宽度,同时不违反所需的小于或等于约束。
The same thing happens with the height constraint. 高度约束也会发生同样的事情。 And since the aspect-ratio constraint is also required, it can only maximize the size of the subview along one axis (unless the container happens to have the same aspect ratio as the subview). 并且由于还需要宽高比约束,它只能沿一个轴最大化子视图的大小(除非容器恰好具有与子视图相同的宽高比)。
So first I create the aspect ratio constraint: 首先我创建宽高比约束:
Then I create equal width and height constraints with the container: 然后我用容器创建相等的宽度和高度约束:
I need to edit these constraints to be less-than-or-equal constraints: 我需要将这些约束编辑为小于或等于约束:
Next I need to create another set of equal width and height constraints with the container: 接下来,我需要使用容器创建另一组相等的宽度和高度约束:
And I need to make these new constraints less than required priority: 我需要使这些新约束低于所需的优先级:
Finally, you asked for the subview to be centered in its container, so I'll set up those constraints: 最后,您要求子视图在其容器中居中,因此我将设置这些约束:
Now, to test, I'll select the view controller and ask Xcode to update all the frames. 现在,为了测试,我将选择视图控制器并让Xcode更新所有帧。 This is what I get: 这就是我得到的:
Oops! 哎呀! The subview has expanded to completely fill its container. 子视图已扩展为完全填充其容器。 If I select it, I can see that in fact it's maintained its aspect ratio, but it's doing an aspect- fill instead of an aspect- fit . 如果我选择它,我可以看到它实际上保持了它的宽高比,但它正在进行方面填充而不是方面适合 。
The problem is that on a less-than-or-equal constraint, it matters which view is at each end of the constraint, and Xcode has set up the constraint opposite from my expectation. 问题在于,在约束小于或等于的情况下,哪个视图位于约束的每一端都很重要,并且Xcode设置了与我的期望相反的约束。 I could select each of the two constraints and reverse its first and second items. 我可以选择两个约束中的每一个并反转它的第一个和第二个项目。 Instead, I'll just select the subview and change the constraints to be greater-than-or-equal: 相反,我只需选择子视图并将约束更改为大于或等于:
Xcode updates the layout: Xcode更新布局:
Now I do all the same things to the dark green view on the bottom. 现在我对底部的深绿色视图做了所有相同的事情。 I need to make sure its aspect ratio is 1:4 (Xcode resized it in a weird way since it didn't have constraints). 我需要确保它的宽高比为1:4(Xcode以奇怪的方式调整大小,因为它没有约束)。 I won't show the steps again since they're the same. 我不会再显示步骤,因为它们是相同的。 Here's the result: 这是结果:
Now I can run it in the iPhone 4S simulator, which has a different screen size than IB used, and test rotation: 现在我可以在iPhone 4S模拟器中运行它,它的屏幕尺寸与IB使用的不同,并且测试旋转:
And I can test in in the iPhone 6 simulator: 我可以在iPhone 6模拟器中测试:
I've uploaded my final storyboard to this gist for your convenience. 为方便起见,我已将最后的故事板上传到此要点 。
#3楼
Rob, your answer is awesome! 罗布,你的答案太棒了! I also know that this question is specifically about achieving this by using auto-layout. 我也知道这个问题具体是通过使用自动布局来实现这一点。 However, just as a reference, I'd like to show how this can be done in code. 但是,作为参考,我想展示如何在代码中完成此操作。 You set up the top and bottom views (blue and pink) just like Rob showed. 您可以设置顶部和底部视图(蓝色和粉红色),就像Rob所示。 Then you create a custom AspectFitView
: 然后创建一个自定义AspectFitView
:
AspectFitView.h : AspectFitView.h :
#import <UIKit/UIKit.h>
@interface AspectFitView : UIView
@property (nonatomic, strong) UIView *childView;
@end
AspectFitView.m : 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
Next, you change the class of the blue and pink views in the storyboard to be AspectFitView
s. 接下来,将故事板中的蓝色和粉红色视图的类更改为AspectFitView
。 Finally you set two outlets to your viewcontroller topAspectFitView
and bottomAspectFitView
and set their childView
s in viewDidLoad
: 最后,为viewcontroller topAspectFitView
和bottomAspectFitView
设置两个出口,并在viewDidLoad
设置它们的childView
:
- (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;
}
So it's not hard to do this in code and it is still very adaptable and works with variably-sized views and different aspect ratios. 因此,在代码中执行此操作并不难,它仍然具有很强的适应性,可以使用不同大小的视图和不同的宽高比。
Update July 2015 : Find a demo app here: https://github.com/jfahrenkrug/SPWKAspectFitView 2015年7月更新 :在此处查找演示应用程序: https : //github.com/jfahrenkrug/SPWKAspectFitView
#4楼
I needed a solution from the accepted answer, but executed from the code. 我需要一个接受答案的解决方案,但是从代码中执行。 The most elegant way I've found is using Masonry framework . 我发现最优雅的方法是使用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);
}];
#5楼
I found myself wanting aspect- fill behavior so that a UIImageView would maintain its own aspect ratio and entirely fill the container view. 我发现自己想要方面填充行为,以便UIImageView保持自己的宽高比并完全填充容器视图。 Confusingly, my UIImageView was breaking BOTH high-priority equal-width and equal-height constraints (described in Rob's answer) and rendering at full resolution. 令人困惑的是,我的UIImageView打破了两个高优先级的等宽和等高约束(在Rob的回答中描述)并以全分辨率渲染。
The solution was simply to set the UIImageView's Content Compression Resistance Priority lower than the priority of the equal-width and equal-height constraints: 解决方案只是将UIImageView的内容压缩阻力优先级设置为低于等宽和等高约束的优先级:
#6楼
This is a port of @rob_mayoff's excellent answer to a code-centric approach, using NSLayoutAnchor
objects and ported to Xamarin. 这是@ rob_mayoff对以代码为中心的方法的优秀答案的一个端口,使用NSLayoutAnchor
对象并移植到Xamarin。 For me, NSLayoutAnchor
and related classes have made AutoLayout much easier to program: 对我来说, NSLayoutAnchor
和相关类使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 ();
}
}
下一篇: Masonry的基本使用