我想使用AutoLayout以一种让人联想到UIImageView的方面适合内容模式的方式来大小和布局视图。

我在接口生成器的容器视图中有一个子视图。子视图有一些固有的纵横比,我希望尊重它。容器视图的大小在运行时之前是未知的。

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

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

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

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


当前回答

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

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

其他回答

我发现自己想要aspect-fill行为,这样UIImageView就能保持自己的长宽比并完全填充容器视图。令人困惑的是,我的UIImageView打破了高优先级等宽和等高的限制(在Rob的回答中描述了),并以全分辨率渲染。

解决方案是简单地设置UIImageView的内容压缩阻力优先级低于等宽等高约束的优先级:

我需要一个解决方案从接受的答案,但从代码执行。我发现的最优雅的方式是使用砌体框架。

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

罗伯,你的答案太棒了! 我也知道这个问题是专门关于实现这一点使用自动布局。但是,作为参考,我想展示如何在代码中实现这一点。像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

接下来,您将故事板中蓝色和粉色视图的类更改为AspectFitViews。最后,你设置两个输出到你的视图控制器topAspectFitView和bottomAspectFitView,并在viewDidLoad中设置它们的childViews:

- (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

这是针对macOS的。

我有问题使用罗伯的方式来实现方面适合OS X应用程序。但我用了另一种方法——我没有使用宽度和高度,而是使用了前导、后导、顶部和底部空间。

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

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

然后工作就完成了!

我找不到任何现成的完全程序化的解决方案,所以这里是我在swift 5中对方面填充扩展到视图的看法:

extension UIView {

    public enum FillingMode {
        case full(padding:Int = 0)
        case aspectFit(ratio:CGFloat)
        // case aspectFill ...
    }

    public func addSubview(_ newView:UIView, withFillingMode fillingMode:FillingMode) {
        newView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(newView)

        switch fillingMode {
        case let .full(padding):
            let cgPadding = CGFloat(padding)

            NSLayoutConstraint.activate([
                newView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: cgPadding),
                newView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -cgPadding),
                newView.topAnchor.constraint(equalTo: topAnchor, constant: cgPadding),
                newView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -cgPadding)
            ])
        case let .aspectFit(ratio):
            guard ratio != 0 else { return }

            NSLayoutConstraint.activate([
                newView.centerXAnchor.constraint(equalTo: centerXAnchor),
                newView.centerYAnchor.constraint(equalTo: centerYAnchor),

                newView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor),
                newView.leadingAnchor.constraint(equalTo: leadingAnchor).usingPriority(900),

                newView.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor),
                newView.trailingAnchor.constraint(equalTo: trailingAnchor).usingPriority(900),

                newView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor),
                newView.topAnchor.constraint(equalTo: topAnchor).usingPriority(900),

                newView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor),
                newView.bottomAnchor.constraint(equalTo: bottomAnchor).usingPriority(900),

                newView.heightAnchor.constraint(equalTo: newView.widthAnchor, multiplier: CGFloat(ratio)),
            ])
        }
    }
}

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

extension NSLayoutConstraint {
    /// Returns the constraint sender with the passed priority.
    ///
    /// - Parameter priority: The priority to be set.
    /// - Returns: The sended constraint adjusted with the new priority.
    func usingPriority(_ priority: Int) -> NSLayoutConstraint {
        self.priority = UILayoutPriority( (1...1000 ~= priority) ? Float(priority) : 1000 )
        return self
    }
}

希望能有所帮助~