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


336

AutoLayoutを使用して、UIImageViewのアスペクトフィットコンテンツモードを連想させる方法でビューのサイズとレイアウトを設定します。

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

コンテナビューのアスペクト比がサブビューよりも広い場合、サブビューの高さを親ビューの高さと等しくします。

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

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

Xcode 6または以前のバージョンでAutoLayout制約を使用してこれを達成する方法はありますか?理想的にはInterface Builderを使用しますが、そうでない場合でも、プログラムでこのような制約を定義することは可能です。

回答:


1046

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

とにかく、自動レイアウトでこれを行うことができます。Xcode 5.1以降、IBで完全に実行できます。いくつかのビューから始めましょう:

一部のビュー

薄緑色のビューのアスペクト比は4:1です。濃い緑色のビューのアスペクト比は1:4です。青いビューが画面の上半分を塗りつぶし、ピンクのビューが画面の下半分を塗りつぶすように制約を設定し、それぞれの緑のビューを縦横比を維持しながら拡大して、容器。

最初に、青いビューの4辺すべてに制約を作成します。距離を0にして、各エッジの最も近い隣に固定します。マージンをオフにしてください。

青の制約

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

次に、ピンク色のビューの左端、下端、右端を最も近い隣に固定します。上端が青いビューの下端にすでに制限されているため、上端の制限を設定する必要はありません。

ピンクの制約

また、ピンクとブルーのビューの高さを同じにする必要があります。これにより、それぞれが画面の半分に表示されます。

ここに画像の説明を入力してください

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

配置されたコンテナ

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

ライトグリーンビューにアスペクトフィットするには、5つの制約が必要です。

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

2つの幅の制約を考えてみましょう。ライトグリーンビューの幅を決定するには、それよりも小さいか等しいという制約だけでは不十分です。多くの幅が制約に適合します。あいまいさがあるため、autolayoutは他の(優先度は高いが必須ではない)制約のエラーを最小限に抑えるソリューションを選択しようとします。エラーを最小限に抑えるということは、幅をコンテナの幅にできるだけ近づけることを意味しますが、必要とされる以下の制約に違反しません。

高さの制約でも同じことが起こります。また、アスペクト比の制約も必要なため、1つの軸に沿ってサブビューのサイズを最大化できるだけです(コンテナーがサブビューと同じアスペクト比を持たない限り)。

したがって、最初にアスペクト比の制約を作成します。

トップアスペクト

次に、コンテナで幅と高さの等しい制約を作成します。

トップ等しいサイズ

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

トップ以下のサイズ

次に、コンテナで同じ幅と高さの制約の別のセットを作成する必要があります。

再び同じサイズ

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

上等は必要ありません

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

上部中央

次に、テストするために、ビューコントローラーを選択し、すべてのフレームを更新するようにXcodeに要求します。これは私が得るものです:

不正な上部レイアウト

おっとっと!サブビューが拡張されて、コンテナーが完全にいっぱいになりました。これを選択すると、実際にはアスペクト比が維持されていることがわかりますが、アスペクトフィットではなくアスペクトフィルを実行してます。

問題は、以下の制約では、制約の両端にあるビューが重要であり、Xcodeが私の期待とは逆の制約を設定したことです。2つの制約をそれぞれ選択し、その最初と2番目の項目を逆にすることができます。代わりに、サブビューを選択して、制約を次の値以上に変更します。

上位の制約を修正

Xcodeはレイアウトを更新します。

正しい上部レイアウト

今、私は底の濃い緑色のビューにすべて同じことをします。そのアスペクト比が1:4であることを確認する必要があります(Xcodeには制約がないため、奇妙な方法でサイズが変更されました)。手順は同じなので、再度表示することはありません。結果は次のとおりです。

上部と下部の正しいレイアウト

これで、使用しているIBとは画面サイズが異なるiPhone 4Sシミュレーターで実行し、回転をテストできます。

iphone 4sテスト

そして、私はiPhone 6シミュレータでテストすることができます:

iphone 6テスト

私はあなたの便宜のために私の最終的なストーリーボードをこの要旨にアップロードしました。


27
LICEcapを使用しました。これは無料(GPL)で、GIFに直接記録されます。アニメーションGIFが2 MB以下であれば、他の画像と同じようにこのサイトに投稿できます。
rob mayoff 2014

1
@LucaTorella優先度の高い制約のみでは、レイアウトがあいまいです。 今日あなたが望む結果を得たからといって、それがiOSの次のバージョンで実現するという意味ではありません。 サブビューがアスペクト比1:1を必要とし、スーパービューのサイズが99×100であり、必要なアスペクト比と高優先度の等式制約のみがあるとします。自動レイアウトでは、サブビューをサイズ99x99(合計エラー1)またはサイズ100x100(合計エラー1)に設定できます。1つのサイズはアスペクトフィットです。もう1つはアスペクトフィルです。必要な制約を追加すると、独自の最小エラーソリューションが生成されます。
rob mayoff 2014

1
@LucaTorellaアスペクト比の制約の可用性に関する修正をありがとう。
rob mayoff 2014

11
投稿が1つだけの10,000の優れたアプリ...信じられないほど。
DonVaughn 2014

2
すごい..ここで私が今まで見たスタックオーバーフローでのベストアンサー..このサーありがとう..
0yeoj

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、その設定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


1
@DanielGalaskoありがとう、あなたは正しいです。私は私の答えでそれを述べます。両方のソリューションに含まれる手順を比較するのに役立つリファレンスになると思いました。
Johannes Fahrenkrug 2014年

1
プログラムによる参照も非常に役立ちます。
ctpenrose 2014

@JohannesFahrenkrugチュートリアルプロジェクトがある場合、共有できますか?ありがとうございました:)
Erhan Demirci

1
@ErhanDemirci承知しました。github.com/ jfahrenkrug
Johannes Fahrenkrug 2015

8

受け入れられた回答からの解決策が必要でしたが、コードから実行されました。私が見つけた最もエレガントな方法は、石工フレームワークを使用することです。

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

5

これはmacOS用です。

OS Xアプリケーションでアスペクトフィットを実現するためにRobの方法を使用するのに問題があります。しかし、私は別の方法でそれを作りました-幅と高さを使用する代わりに、私はリーディング、トレーリング、トップとボトムのスペースを使用しました。

基本的に、2つの先行スペースを追加します。1つは優先度が0以上の@ 1000で、もう1つは優先度が0未満の250です。末尾、上部、下部のスペースに同じ設定を行います。

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

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

ここに画像の説明を入力してください ここに画像の説明を入力してください


これをプログラムでどのように実行しますか?
FateNuller 2017

@FateNullerただ次のステップ?
brianLikeApple

4

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

解決策は、UIImageViewのコンテンツ圧縮耐性の優先順位を、幅と高さが等しい制約の優先順位よりも低く設定することでした。

コンテンツ圧縮耐性


2

これは、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

たぶんこれは、アスペクトフィルとストレッチもサポートする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);
        }
    }
}];
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.