ios 在 Xcode 6 中使用 AutoLayout 约束模拟方面适合行为

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25766747/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 02:20:32  来源:igfitidea点击:

Emulating aspect-fit behaviour using AutoLayout constraints in Xcode 6

iosobjective-cxcodeautolayout

提问by chris838

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. The subview has some inherent aspect ratio which I wish to respect. The container view's size is unknown until runtime.

我在 Interface Builder 的容器视图中有一个子视图。子视图有一些我希望尊重的固有纵横比。容器视图的大小在运行之前是未知的。

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? Ideally using Interface Builder, but if not perhaps it is possible to define such constraints programmatically.

有没有办法在 Xcode 6 或以前的版本中使用 AutoLayout 约束来实现这一点?理想情况下使用 Interface Builder,但如果不是,则可以以编程方式定义此类约束。

回答by rob mayoff

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. Let's start with some views:

无论如何,您可以使用自动布局来做到这一点。从 Xcode 5.1 开始,您可以完全在 IB 中完成。让我们从一些观点开始:

some views

一些观点

The light green view has an aspect ratio of 4:1. The dark green view has an aspect ratio of 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.

浅绿色视图的纵横比为 4:1。深绿色视图的纵横比为 1:4。我将设置约束,以便蓝色视图填充屏幕的上半部分,粉红色视图填充屏幕的下半部分,并且每个绿色视图在保持其纵横比并适合其的同时尽可能地扩展容器。

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。我确保关闭边距:

blue constraints

蓝色约束

Note that I don'tupdate 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.

接下来,我将粉红色视图的左、下和右边缘固定到其最近的邻居。我不需要设置顶边约束,因为它的顶边已经被限制在蓝色视图的底边。

pink constraints

粉色约束

I also need an equal-heights constraint between the pink and blue views. This will make them each fill half the screen:

我还需要粉红色和蓝色视图之间的等高约束。这将使它们每个填满屏幕的一半:

enter image description here

在此处输入图片说明

If I tell Xcode to update all the frames now, I get this:

如果我告诉 Xcode 现在更新所有帧,我会得到:

containers laid out

集装箱布置

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.
  • 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.
  • 浅绿色视图上的必需优先级纵横比约束。您可以使用 Xcode 5.1 或更高版本在 xib 或故事板中创建此约束。
  • 一个必需的优先级约束,将浅绿色视图的宽度限制为小于或等于其容器的宽度。
  • 高优先级约束将浅绿色视图的宽度设置为其容器的宽度。
  • 一个必需的优先级约束,将浅绿色视图的高度限制为小于或等于其容器的高度。
  • 一个高优先级约束,将浅绿色视图的高度设置为其容器的高度。

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. 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:

所以首先我创建纵横比约束:

top aspect

顶面

Then I create equal width and height constraints with the container:

然后我用容器创建相等的宽度和高度约束:

top equal size

顶部相等尺寸

I need to edit these constraints to be less-than-or-equal constraints:

我需要将这些约束编辑为小于或等于约束:

top less than or equal size

顶部小于或等于大小

Next I need to create another set of equal width and height constraints with the container:

接下来我需要为容器创建另一组等宽和高度的约束:

top equal size again

再次顶部相同大小

And I need to make these new constraints less than required priority:

我需要使这些新约束低于所需的优先级:

top equal not required

顶部等于不需要

Finally, you asked for the subview to be centered in its container, so I'll set up those constraints:

最后,您要求子视图在其容器中居中,因此我将设置这些约束:

top centered

顶部居中

Now, to test, I'll select the view controller and ask Xcode to update all the frames. This is what I get:

现在,为了测试,我将选择视图控制器并要求 Xcode 更新所有帧。这就是我得到的:

incorrect top layout

顶部布局不正确

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-fillinstead 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. 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 设置的约束与我的预期相反。我可以选择两个约束中的每一个并反转其第一项和第二项。相反,我将只选择子视图并将约束更改为大于或等于:

fix top constraints

修复顶部约束

Xcode updates the layout:

Xcode 更新布局:

correct top layout

正确的顶部布局

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). I won't show the steps again since they're the same. Here's the result:

现在我对底部的深绿色视图执行所有相同的操作。我需要确保它的纵横比是 1:4(Xcode 以一种奇怪的方式调整了它的大小,因为它没有限制)。我不会再显示这些步骤,因为它们是相同的。结果如下:

correct top and bottom layout

正确的顶部和底部布局

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 不同,并测试旋转:

iphone 4s test

iphone 4s 测试

And I can test in in the iPhone 6 simulator:

我可以在 iPhone 6 模拟器中进行测试:

iphone 6 test

iphone 6 测试

I've uploaded my final storyboard to this gistfor your convenience.

为方便起见,我已将我的最终故事板上传到此要点

回答by Johannes Fahrenkrug

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. Then you create a custom AspectFitView:

罗布,你的回答很棒!我也知道这个问题是专门关于通过使用自动布局来实现的。但是,作为参考,我想展示如何在代码中完成此操作。就像 Rob 展示的那样,您设置了顶部和底部视图(蓝色和粉红色)。然后你创建一个自定义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 AspectFitViews. Finally you set two outlets to your viewcontroller topAspectFitViewand bottomAspectFitViewand set their childViews in viewDidLoad:

接下来,您将故事板中蓝色和粉红色视图的类更改为AspectFitViews。最后,您将两个出口设置到您的视图控制器topAspectFitViewbottomAspectFitView并将它们的childViews设置为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;
}

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

回答by Tomasz B?k

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 framework

#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);
}];

回答by Gregor

I found myself wanting aspect-fillbehavior so that a UIImageView would maintain its own aspect ratio and entirely fill the container view. 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 保持自己的纵横比并完全填充容器视图。令人困惑的是,我的 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 的 Content Compression Resistance Priority 设置为低于等宽等高约束的优先级:

Content Compression Resistance

内容抗压性

回答by brianLikeApple

This is for macOS.

这适用于 macOS。

I have problem to use Rob's way to achieve aspect fit on the OS X application. But I made it with another way -- Instead of using width and height, I used leading, trailing, top and bottom space.

我在使用 Rob 的方式在 OS X 应用程序上实现方面拟合时遇到问题。但我用另一种方式制作它——我没有使用宽度和高度,而是使用前导、尾随、顶部和底部空间。

Basically, add two leading spaces where one is >= 0 @1000 required priority and another one is = 0 @250 low priority. Do same settings to trailing, top and bottom space.

基本上,添加两个前导空格,其中一个是 >= 0 @1000 所需优先级,另一个是 = 0 @250 低优先级。对尾随、顶部和底部空间进行相同的设置。

Of course, you need to set aspect ratio and centre X and centre Y.

当然,您需要设置纵横比和中心 X 和中心 Y。

And then job's done!

然后工作就完成了!

enter image description hereenter image description here

在此处输入图片说明在此处输入图片说明

回答by Larry OBrien

This is a port of @rob_mayoff's excellent answer to a code-centric approach, using NSLayoutAnchorobjects and ported to Xamarin. For me, NSLayoutAnchorand related classes have made AutoLayout much easier to program:

这是@rob_mayoff 对以代码为中心的方法的出色回答的一个端口,使用NSLayoutAnchor对象并移植到 Xamarin。对我来说,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 ();
    }
}

回答by Mr. Ming

Maybe this is the shortest answer, with Masonry, which also supports aspect-fill and stretch.

也许这是最短的答案,使用Masonry,它也支持方面填充和拉伸。

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);
        }
    }
}];