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

时间:2014-09-10 13:28:11

标签: ios objective-c xcode autolayout

我想使用AutoLayout以一种让人想起UIImageView的宽高比内容模式的方式来调整和布局视图。

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

如果容器视图的宽高比比子视图宽,那么我希望子视图的高度等于父视图的高度。

如果容器视图的宽高比高于子视图,那么我希望子视图的宽度等于父视图的宽度。

在任何一种情况下,我希望子视图在容器视图中水平和垂直居中。

有没有办法在Xcode 6或之前版本中使用AutoLayout约束来实现这一目的?理想情况下使用Interface Builder,但如果没有,也许可以通过编程方式定义这些约束。

8 个答案:

答案 0 :(得分:1034)

你并没有描述适合规模;你正在描述方面适合度。 (我已经在这方面编辑了你的问题。)子视图变得尽可能大,同时保持其纵横比并完全适合其父级。

无论如何,您可以使用自动布局执行此操作。你可以在IB Xcode 5.1中完全完成它。让我们从一些观点开始:

some views

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

首先,我将在蓝色视图的所有四个边上创建约束。我将它固定到每条边上最近的邻居,距离为0.我一定要关闭边距:

blue constraints

请注意,我没有更新框架。我发现在设置约束时在视图之间留出空间更容易,只需手动将常量设置为0(或其他)。

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

pink constraints

我还需要粉红色和蓝色视图之间的等高限制。这将使他们每个填充屏幕的一半:

enter image description here

如果我告诉Xcode现在更新所有帧,我明白了:

containers laid out

所以我到目前为止设置的限制是正确的。我将其撤消并开始处理浅绿色视图。

适合浅绿色视图的宽高比需要五个约束:

  • 浅绿色视图上的必需优先宽高比限制。您可以使用Xcode 5.1或更高版本在xib或storyboard中创建此约束。
  • 限制浅绿色视图宽度的必需优先级约束小于或等于其容器的宽度。
  • 高优先级约束,将浅绿色视图的宽度设置为等于其容器的宽度。
  • 限制浅绿色视图高度的必需优先级约束小于或等于其容器的高度。
  • 高优先级约束,将浅绿色视图的高度设置为等于其容器的高度。

让我们考虑两个宽度约束。小于或等于约束本身不足以确定浅绿色视图的宽度;许多宽度将适合约束。由于存在歧义,autolayout将尝试选择最小化另一个(高优先级但不是必需的)约束中的错误的解决方案。最小化错误意味着使宽度尽可能接近容器的宽度,同时不违反所需的小于或等于约束。

高度约束也会发生同样的事情。并且由于还需要宽高比约束,它只能沿一个轴最大化子视图的大小(除非容器恰好具有与子视图相同的宽高比)。

首先我创建宽高比约束:

top aspect

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

top equal size

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

top less than or equal size

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

top equal size again

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

top equal not required

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

top centered

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

incorrect top layout

糟糕!子视图已扩展为完全填充其容器。如果我选择它,我可以看到它实际上保持了它的宽高比,但它做了一个方面 - 填充而不是方面 - 适合

问题在于,在一个小于或等于约束的情况下,哪个视图位于约束的每一端都很重要,并且Xcode设置了与我的期望相反的约束。我可以选择两个约束中的每一个并反转它的第一个和第二个项目。相反,我只是选择子视图并将约束更改为大于或等于:

fix top constraints

Xcode更新布局:

correct top layout

现在,我对底部的深绿色视图做了同样的事情。我需要确保它的宽高比是1:4(Xcode以一种奇怪的方式调整它的大小,因为它没有约束)。我不会再显示这些步骤,因为它们是相同的。结果如下:

correct top and bottom layout

现在我可以在iPhone 4S模拟器中运行它,它的屏幕尺寸与IB使用的不同,并且测试旋转:

iphone 4s test

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

iphone 6 test

为了您的方便,我已将最终的故事板上传到this gist

答案 1 :(得分:24)

罗布,你的答案太棒了! 我也知道这个问题具体是通过使用自动布局来实现这一点。但是,作为参考,我想展示如何在代码中完成此操作。您可以设置顶部和底部视图(蓝色和粉红色),就像Rob所示。然后,您创建自定义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

接下来,您将故事板中的蓝色和粉红色视图的类更改为AspectFitView。最后,您为viewcontroller topAspectFitViewbottomAspectFitView设置了两个出口,并在childView中设置了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;
}

因此,在代码中执行此操作并不困难,它仍然具有很强的适应性,适用于大小不同的视图和不同的宽高比。

2015年7月更新:在此处找到演示应用:https://github.com/jfahrenkrug/SPWKAspectFitView

答案 2 :(得分:7)

我需要一个接受答案的解决方案,但是从代码中执行。我找到的最优雅的方法是使用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);
}];

答案 3 :(得分:4)

这适用于macOS。

我有问题使用Rob的方式来实现OS X应用程序的方面适应性。但我用另一种方式制作它 - 而不是使用宽度和高度,我使用了前导,尾随,顶部和底部空间。

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

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

然后工作完成了!

enter image description here enter image description here

答案 4 :(得分:3)

我发现自己想要方面 - 填充行为,以便UIImageView保持自己的宽高比并完全填充容器视图。令人困惑的是,我的UIImageView打破了两个高优先级的等宽和等高约束(在Rob的回答中描述)并以全分辨率渲染。

解决方案只是将UIImageView的内容压缩阻力优先级设置为低于等宽和等高约束的优先级:

Content Compression Resistance

答案 5 :(得分:1)

这是@ 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 ();
    }
}

答案 6 :(得分:1)

也许这是最短的答案,Masonry,也支持纵横填充和拉伸。

alter PROC a
 AS
     declare @ret int;

    BEGIN   
    BEGIN TRY
        BEGIN TRANSACTION;

        IF ( 1 = 1 )
            BEGIN
                DECLARE @ReturnMessage VARCHAR(50);
                EXEC @ret = dbo.b @ReturnMessage = @ReturnMessage OUTPUT; -- varchar(50)
                if @ret <> 0
                begin
                    ;throw 50000, 'ErrorA', 1;
                end

                IF ( @ReturnMessage = 'aaaa' )
                begin
                    PRINT 'anuj';
                    end
            END;
        COMMIT   TRANSACTION;
    END TRY
    BEGIN CATCH
        if @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;
        THROW;
    END CATCH;
END;

alter PROC b
(
  @ReturnMessage VARCHAR(50) OUTPUT
)
AS
BEGIN
    BEGIN TRY
        BEGIN TRANSACTION;
        IF ( 1 = 1 )
            BEGIN
                SET @ReturnMessage = 'aaaa';
                throw 50000, 'ErrorB', 1;
                RETURN 1;
            END;
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        if @@trancount > 0
            ROLLBACK TRANSACTION;
        THROW;
    END CATCH;

END;

答案 7 :(得分:0)

我找不到任何现成的完全编程解决方案,所以这是我对视图的方面填充扩展名的 swift 5 : / p>

WRITE_EXTERNAL_STORAGE

这是优先级扩展(来自另一个线程,但是我用1 ... 1000之间的某些模式匹配来“保护它”:

<!-- OR ANY OTHER AMD LOADER HERE INSTEAD OF loader.js -->
<script src="../node_modules/monaco-editor/min/vs/loader.js"></script>
<script>
    require.config({ paths: { 'vs': '../node_modules/monaco-editor/min/vs' }});

    require(['vs/editor/editor.main'], function() {
        var editor = monaco.editor.create(document.getElementById('container'), {
            value: [
                'function x() {',
                '\tconsole.log("Hello world!");',
                '}'
            ].join('\n'),
            language: 'javascript'
        });
    });
</script>

希望有帮助〜