AutoLayoutを使用して、UIImageViewのアスペクトに合ったコンテンツモードを彷彿とさせるようにビューのサイズとレイアウトを設定します。
Interface Builderのコンテナビュー内にサブビューがあります。サブビューは私が尊重したいいくつかの固有のアスペクト比を持っています。コンテナビューのサイズは実行時まで不明です。
コンテナビューの縦横比がサブビューよりも広い場合は、サブビューの高さを親ビューの高さと同じにします。
コンテナビューの縦横比がサブビューよりも高い場合は、サブビューの幅を親ビューの幅と同じにします。
どちらの場合でも、サブビューをコンテナビュー内で水平方向と垂直方向の中央に配置します。
Xcode 6または以前のバージョンのオートレイアウト制約を使用してこれを実現する方法はありますか?理想的にはInterface Builderを使いますが、もしそうでなければ、そのような制約をプログラム的に定義することは可能です。
あなたはスケールに合わせて説明していません。あなたはアスペクトフィットについて説明しています。 (私はこの点に関してあなたの質問を編集しました。)サブビューはそのアスペクト比を維持しながら完全にその親の内側にフィットしながらできるだけ大きくなります。
とにかく、あなたは自動レイアウトでこれをすることができます。あなたはXcode 5.1の時点で完全にIBでそれをすることができます。いくつかのビューから始めましょう。
薄緑色のビューの縦横比は4:1です。濃い緑色のビューの縦横比は1:4です。青のビューが画面の上半分を占め、ピンクのビューが画面の下半分を占め、それぞれの緑のビューが縦横比を保ったままできるだけ拡大するように、制約を設定します。容器。
まず、青いビューの四方すべてに制約を作成します。各Edgeの最も近いネイバーに距離0で固定します。余白は必ずオフにします。
I しない まだフレームを更新しないことに注意してください。制約を設定するときにビュー間にスペースを空けるのが簡単で、手動で定数を0(または何でも)に設定するだけです。
次に、ピンク色のビューの左端、下端、右端を一番近いピンに固定します。トップエッジはすでにブルービューのボトムエッジに制約されているので、トップエッジ制約を設定する必要はありません。
また、ピンクとブルーのビュー間の高さの制限も必要です。これにより、それらはそれぞれ画面の半分を占めるようになります。
Xcodeに今すぐすべてのフレームを更新するように指示すると、次のようになります。
だから私はこれまでに設定した制約は正しいです。元に戻して、薄緑色のビューで作業を開始します。
薄緑色のビューをアスペクトフィットするには、5つの制約が必要です。
2つの幅の制約を考えてみましょう。 「以下」の制約だけでは、薄緑色のビューの幅を決定するのに十分ではありません。多くの幅が制約に合います。あいまいさがあるので、autolayoutは他の(優先順位が高いが必須ではない)制約のエラーを最小にするソリューションを選択しようとします。エラーを最小限に抑えることは、幅をコンテナの幅にできるだけ近づける一方で、必要な以下の制約に違反しないことを意味します。
高さの制限についても同じことが起こります。また、縦横比の制約も必要なので、1つの軸に沿ってサブビューのサイズを最大化することしかできません(コンテナの縦横比がサブビューと同じでない限り)。
そこで最初に縦横比の制約を作成します。
それからコンテナを使って幅と高さを同じにします。
これらの制約を、以下の制約に編集する必要があります。
次に、コンテナを使って、幅と高さの等しい同じ拘束をもう1組作成する必要があります。
そして、私はこれらの新しい制約を必要な優先順位よりも低くする必要があります。
最後に、サブビューをそのコンテナの中央に配置するように依頼したので、これらの制約を設定します。
それでは、テストするために、View Controllerを選択し、Xcodeにすべてのフレームを更新するように依頼します。これは私が得るものです:
おっとっと!サブビューはそのコンテナーを完全に埋めるように拡張されました。選択すると、実際は縦横比は維持されていますが、アスペクト - fit ではなくアスペクト - fill を実行しています。
問題は、以下の制約では、どちらのビューが制約の両端にあるかが重要であり、Xcodeが私の予想とは反対に制約を設定していることです。 2つの制約のそれぞれを選択して、その最初と2番目の項目を逆にすることができます。代わりに、サブビューを選択して、制約が「以上」になるように変更します。
Xcodeがレイアウトを更新します。
今度は私は底の深緑色の眺めにすべて同じ事をする。縦横比が1:4であることを確認する必要があります(Xcodeには制約がないため、奇妙なサイズに変更されました)。手順は同じなので、ここでは説明しません。これが結果です。
これで、iPhone 4Sシミュレータで実行できるようになりました。これは、IBとは異なる画面サイズで、回転をテストします。
そして、私はiPhone 6シミュレータでテストすることができます:
私の最後のストーリーボードを this Gist にアップロードしました。
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
次に、ストーリーボードの青とピンクのビューのクラスをAspectFitView
sに変更します。最後に、ビューコントローラtopAspectFitView
とbottomAspectFitView
に2つのアウトレットを設定し、それらの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
私は受け入れられた答えからの解決策を必要としましたが、コードから実行されました。私が見つけた最も洗練された方法は、 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);
}];
UIImageViewが独自のアスペクト比を維持し、コンテナビューを完全に埋めるように、アスペクト - fillの動作を望んでいます。紛らわしいことに、私のUIImageViewは(Robの答えで説明されている)両方の高優先度の等幅および等高さの制約を破り、フル解像度でレンダリングしていました。
解決策は、単にUIImageViewのContent Compression Resistance Priorityをequal-widthおよびequal-heightの制約の優先度よりも低く設定することでした。
これは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 ();
}
}
たぶん、これが 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);
}
}
}];