web-dev-qa-db-ja.com

Xcode 6でオートレイアウト制約を使用してアスペクトフィット動作をエミュレートする

AutoLayoutを使用して、UIImageViewのアスペクトに合ったコンテンツモードを彷彿とさせるようにビューのサイズとレイアウトを設定します。

Interface Builderのコンテナビュー内にサブビューがあります。サブビューは私が尊重したいいくつかの固有のアスペクト比を持っています。コンテナビューのサイズは実行時まで不明です。

コンテナビューの縦横比がサブビューよりも広い場合は、サブビューの高さを親ビューの高さと同じにします。

コンテナビューの縦横比がサブビューよりも高い場合は、サブビューの幅を親ビューの幅と同じにします。

どちらの場合でも、サブビューをコンテナビュー内で水平方向と垂直方向の中央に配置します。

Xcode 6または以前のバージョンのオートレイアウト制約を使用してこれを実現する方法はありますか?理想的にはInterface Builderを使いますが、もしそうでなければ、そのような制約をプログラム的に定義することは可能です。

326
chris838

あなたはスケールに合わせて説明していません。あなたはアスペクトフィットについて説明しています。 (私はこの点に関してあなたの質問を編集しました。)サブビューはそのアスペクト比を維持しながら完全にその親の内側にフィットしながらできるだけ大きくなります。

とにかく、あなたは自動レイアウトでこれをすることができます。あなたはXcode 5.1の時点で完全にIBでそれをすることができます。いくつかのビューから始めましょう。

some views

薄緑色のビューの縦横比は4:1です。濃い緑色のビューの縦横比は1:4です。青のビューが画面の上半分を占め、ピンクのビューが画面の下半分を占め、それぞれの緑のビューが縦横比を保ったままできるだけ拡大するように、制約を設定します。容器。

まず、青いビューの四方すべてに制約を作成します。各Edgeの最も近いネイバーに距離0で固定します。余白は必ずオフにします。

blue constraints

I しない まだフレームを更新しないことに注意してください。制約を設定するときにビュー間にスペースを空けるのが簡単で、手動で定数を0(または何でも)に設定するだけです。

次に、ピンク色のビューの左端、下端、右端を一番近いピンに固定します。トップエッジはすでにブルービューのボトムエッジに制約されているので、トップエッジ制約を設定する必要はありません。

pink constraints

また、ピンクとブルーのビュー間の高さの制限も必要です。これにより、それらはそれぞれ画面の半分を占めるようになります。

enter image description here

Xcodeに今すぐすべてのフレームを更新するように指示すると、次のようになります。

containers laid out

だから私はこれまでに設定した制約は正しいです。元に戻して、薄緑色のビューで作業を開始します。

薄緑色のビューをアスペクトフィットするには、5つの制約が必要です。

  • 薄緑色のビューに対する必要優先アスペクト比の制約。 Xcode 5.1以降を使用して、xibまたはストーリーボードでこの制約を作成できます。
  • 薄緑色のビューの幅をそのコンテナーの幅以下に制限する必要優先順位の制約。
  • 薄緑色のビューの幅をそのコンテナーの幅と等しくなるように設定する、優先順位の高い制約です。
  • 薄緑色のビューの高さをそのコンテナーの高さ以下に制限する必要優先順位の制約。
  • 薄緑色のビューの高さをそのコンテナーの高さと等しくなるように設定する、優先順位の高い制約です。

2つの幅の制約を考えてみましょう。 「以下」の制約だけでは、薄緑色のビューの幅を決定するのに十分ではありません。多くの幅が制約に合います。あいまいさがあるので、autolayoutは他の(優先順位が高いが必須ではない)制約のエラーを最小にするソリューションを選択しようとします。エラーを最小限に抑えることは、幅をコンテナの幅にできるだけ近づける一方で、必要な以下の制約に違反しないことを意味します。

高さの制限についても同じことが起こります。また、縦横比の制約も必要なので、1つの軸に沿ってサブビューのサイズを最大化することしかできません(コンテナの縦横比がサブビューと同じでない限り)。

そこで最初に縦横比の制約を作成します。

top aspect

それからコンテナを使って幅と高さを同じにします。

top equal size

これらの制約を、以下の制約に編集する必要があります。

top less than or equal size

次に、コンテナを使って、幅と高さの等しい同じ拘束をもう1組作成する必要があります。

top equal size again

そして、私はこれらの新しい制約を必要な優先順位よりも低くする必要があります。

top equal not required

最後に、サブビューをそのコンテナの中央に配置するように依頼したので、これらの制約を設定します。

top centered

それでは、テストするために、View Controllerを選択し、Xcodeにすべてのフレームを更新するように依頼します。これは私が得るものです:

incorrect top layout

おっとっと!サブビューはそのコンテナーを完全に埋めるように拡張されました。選択すると、実際は縦横比は維持されていますが、アスペクト - fit ではなくアスペクト - fill を実行しています。

問題は、以下の制約では、どちらのビューが制約の両端にあるかが重要であり、Xcodeが私の予想とは反対に制約を設定していることです。 2つの制約のそれぞれを選択して、その最初と2番目の項目を逆にすることができます。代わりに、サブビューを選択して、制約が「以上」になるように変更します。

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 にアップロードしました。

1034
rob mayoff

Rob、あなたの答えは素晴らしいです!私はまた、この質問が自動レイアウトを使用してこれを達成することに関するものであることも知っています。ただし、参考までに、これをコードで実行する方法を説明します。 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に変更します。最後に、ビューコントローラtopAspectFitViewbottomAspectFitViewに2つのアウトレットを設定し、それらのchildViewviewDidLoadに設定します。

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

24

私は受け入れられた答えからの解決策を必要としましたが、コードから実行されました。私が見つけた最も洗練された方法は、 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);
}];
7
Tomasz Bąk

これはmacOS用です。

私はRobの方法を使ってOS Xアプリケーションにぴったり合うようにすることに問題があります。しかし私は別の方法でそれを作りました - 幅と高さを使う代わりに、私は先行、後続、上部と下部のスペースを使いました。

基本的に、2つの先行スペースを追加します。1つは> = 0 @ 1000が必要な優先順位で、もう1つは= 0 @ 250の低い優先順位です。末尾のスペース、上下のスペースにも同じ設定をします。

もちろん、アスペクト比と中心Xと中心Yを設定する必要があります。

そして仕事は終わりました!

enter image description hereenter image description here

4
brianLikeApple

UIImageViewが独自のアスペクト比を維持し、コンテナビューを完全に埋めるように、アスペクト - fillの動作を望んでいます。紛らわしいことに、私のUIImageViewは(Robの答えで説明されている)両方の高優先度の等幅および等高さの制約を破り、フル解像度でレンダリングしていました。

解決策は、単にUIImageViewのContent Compression Resistance Priorityをequal-widthおよびequal-heightの制約の優先度よりも低く設定することでした。

Content Compression Resistance

3
Gregor

これはNSLayoutAnchorオブジェクトを使用してXamarinに移植された、コード中心のアプローチに対する@ rob_mayoffの優れた答えの移植です。私にとっては、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 ();
    }
}
1
Larry OBrien

たぶん、これが 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);
        }
    }
}];
1
iwill