首页 文章

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

提问于
浏览
321

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

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

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

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

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

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

7 回答

  • 1

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

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

    some views

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

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

    blue constraints

    请注意,我 don't 尚未更新帧 . 我发现在设置约束时在视图之间留出空间更容易,只需手动将常量设置为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

    哎呀!子视图已扩展为完全填充其容器 . 如果我选择它,我可以看到它实际上是's maintained its aspect ratio, but it'正在做一个方面 fill 而不是方面 fit .

    问题在于,在约束小于或等于的情况下,哪个视图位于约束的每一端都很重要,并且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 .

  • 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 s . 最后,为viewcontroller topAspectFitViewbottomAspectFitView 设置两个出口,并在 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;
    }
    

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

    Update July 2015 :在此处查找演示应用:https://github.com/jfahrenkrug/SPWKAspectFitView

  • 6

    我需要一个接受答案的解决方案,但是从代码中执行 . 我发现最优雅的方式是使用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);
    }];
    
  • 0

    This is for macOS.

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

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

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

    然后工作完成了!

    enter image description here

    enter image description here

  • 1028

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

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

    Content Compression Resistance

  • 4

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

    也许这是最短的答案,Masonry .

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

相关问题