Xcode 6에서 자동 레이아웃 제약 조건을 사용하여 종횡비 동작 모방


336

자동 레이아웃을 사용하여 UIImageView의 종횡비 컨텐츠 모드를 연상시키는 방식으로 뷰의 크기와 레이아웃을 지정하고 싶습니다.

Interface Builder의 컨테이너 뷰 안에 하위 뷰가 있습니다. 하위보기에는 내가 존중하고 싶은 고유 종횡비가 있습니다. 컨테이너 뷰의 크기는 런타임까지 알 수 없습니다.

컨테이너 뷰의 종횡비가 하위 뷰보다 넓은 경우 하위 뷰의 높이가 부모 뷰의 높이와 같도록하고 싶습니다.

컨테이너보기의 종횡비가 하위보기보다 높으면 하위보기의 너비가 부모보기의 너비와 같기를 원합니다.

두 경우 모두 하위 뷰를 컨테이너 뷰 내에서 가로 및 세로로 가운데에 맞추기를 원합니다.

Xcode 6 또는 이전 버전에서 AutoLayout 제약 조건을 사용하여 이것을 달성하는 방법이 있습니까? 이상적으로 Interface Builder를 사용하지만 가능하지 않은 경우 프로그래밍 방식으로 이러한 제약 조건을 정의 할 수 있습니다.

답변:


1046

스케일 투 핏을 설명하지 않습니다. 당신은 종횡비를 설명하고 있습니다. (이 점에서 귀하의 질문을 편집했습니다.) 종횡비를 유지하고 부모 내부에 완전히 들어가면서 하위 뷰가 최대한 커집니다.

어쨌든 자동 레이아웃 으로이 작업을 수행 할 수 있습니다. Xcode 5.1부터 IB에서 완전히 수행 할 수 있습니다. 몇 가지 견해부터 시작해 보자.

일부 견해

연한 녹색 화면의 종횡비는 4 : 1입니다. 짙은 녹색 뷰의 종횡비는 1 : 4입니다. 파란색보기가 화면의 절반을 채우고 분홍색보기가 화면의 아래쪽 절반을 채우고 각 녹색보기가 가로 세로 비율을 유지하면서 화면 크기에 맞게 최대한 확장되도록 제약 조건을 설정하려고합니다. 컨테이너.

먼저 파란색 뷰의 네면 모두에 구속 조건을 작성합니다. 거리를 0으로하여 각 가장자리에서 가장 가까운 이웃에 고정합니다. 여백을 해제해야합니다.

블루 제약

내가 참고 하지 않습니다 아직 프레임을 업데이트합니다. 제약 조건을 설정할 때 뷰 사이에 공간을 두는 것이 더 쉽고 상수를 0 (또는 무엇이든)으로 직접 설정하면됩니다.

다음으로 분홍색보기의 왼쪽, 아래쪽 및 오른쪽 가장자리를 가장 가까운 이웃에 고정시킵니다. 상단 가장자리가 이미 파란색보기의 하단 가장자리로 제한되어 있기 때문에 상단 가장자리 제약 조건을 설정할 필요가 없습니다.

분홍색 제약

또한 분홍색과 파란색보기 사이의 동일한 높이 제약이 필요합니다. 이렇게하면 각각 화면의 절반을 채 웁니다.

여기에 이미지 설명을 입력하십시오

Xcode에 모든 프레임을 업데이트하도록 지시하면 다음과 같이됩니다.

컨테이너 배치

그래서 지금까지 설정 한 제약 조건이 맞습니다. 나는 그것을 취소하고 밝은 녹색보기에서 작업을 시작합니다.

연한 녹색 뷰를 화면에 맞추려면 다음과 같은 다섯 가지 제약 조건이 필요합니다.

  • 연두색보기에 필요한 우선 순위 종횡비 제약 조건. Xcode 5.1 이상이있는 xib 또는 스토리 보드에서이 제한 조건을 작성할 수 있습니다.
  • 연한 녹색 뷰의 너비를 컨테이너 너비보다 작거나 같게 제한하는 데 필요한 우선 순위 제약 조건입니다.
  • 밝은 초록색보기의 너비를 컨테이너 너비와 동일하게 설정하는 우선 순위가 높은 제한 조건입니다.
  • 밝은 초록색보기의 높이를 컨테이너의 높이보다 작거나 같게 제한하는 필수 우선 순위 제약 조건입니다.
  • 밝은 초록색보기의 높이를 컨테이너의 높이와 동일하게 설정하는 우선 순위가 높은 제한 조건입니다.

두 가지 너비 제약 조건을 고려해 봅시다. 동일하지 않은 구속 조건만으로는 연두색 시야의 폭을 결정하기에 충분하지 않습니다. 많은 너비가 구속 조건에 맞습니다. 모호성이 있기 때문에 자동 레이아웃은 다른 (높은 우선 순위는 아니지만 필수) 제약 조건의 오류를 최소화하는 솔루션을 선택하려고합니다. 오류를 최소화한다는 것은 필요한 너비를 같거나 적은 제약 조건을 위반하지 않으면 서 컨테이너 너비에 최대한 가깝게 만드는 것을 의미합니다.

높이 제약 조건에서도 마찬가지입니다. 또한 종횡비 제약이 필요하기 때문에 컨테이너가 서브 뷰와 동일한 종횡비를 가지지 않는 한 하나의 축을 따라 서브 뷰의 크기 만 최대화 할 수 있습니다.

먼저 가로 세로 비율 제약 조건을 만듭니다.

최고 양상

그런 다음 컨테이너와 동일한 너비 및 높이 제약 조건을 만듭니다.

최고 크기

이 구속 조건을 동일하거나 덜 구속 조건으로 편집해야합니다.

작거나 같은 크기

다음으로 컨테이너와 동일한 너비 및 높이 제약 조건 세트를 만들어야합니다.

다시 같은 크기

그리고 이러한 새로운 제약 조건을 필요한 우선 순위보다 작게 만들어야합니다.

최고 동등 필요하지 않음

마지막으로 하위 뷰를 컨테이너의 중앙에 배치하도록 요청 했으므로 이러한 제약 조건을 설정하겠습니다.

상단 중앙

이제 테스트하기 위해 뷰 컨트롤러를 선택하고 Xcode에 모든 프레임을 업데이트하도록 요청합니다. 이것이 내가 얻는 것입니다 :

잘못된 상단 레이아웃

죄송합니다! 하위 뷰가 컨테이너를 완전히 채우도록 확장되었습니다. 선택하면 가로 세로 비율이 유지되지만 가로 세로 맞춤 대신 가로 세로 채우기 를하고 있음을 알 있습니다.

문제는 동일하지 않은 제약 조건에서 제약 조건의 각 끝에있는 뷰가 중요하며 Xcode가 내 기대와 반대되는 제약 조건을 설정했다는 것입니다. 두 가지 제약 조건을 각각 선택하고 첫 번째 항목과 두 번째 항목을 되돌릴 수 있습니다. 대신, 하위 뷰를 선택하고 제약 조건을보다 크거나 같게 변경하면됩니다.

상단 제약 조건 수정

Xcode는 레이아웃을 업데이트합니다 :

올바른 상단 레이아웃

이제 바닥의 짙은 녹색으로 동일한 작업을 수행합니다. 가로 세로 비율이 1 : 4인지 확인해야합니다 (Xcode는 제약이 없으므로 이상한 방식으로 크기를 조정했습니다). 단계가 동일하므로 단계를 다시 표시하지 않습니다. 결과는 다음과 같습니다.

올바른 상단 및 하단 레이아웃

이제 IB와 다른 화면 크기를 가진 iPhone 4S 시뮬레이터에서 실행하고 회전을 테스트 할 수 있습니다.

아이폰 4S 테스트

그리고 iPhone 6 시뮬레이터에서 테스트 할 수 있습니다.

아이폰 6 테스트

편의를 위해 최종 스토리 보드를 이 요지 에 업로드했습니다 .


27
LICEcap을 사용 했습니다 . 무료 (GPL)이며 GIF에 직접 기록됩니다. 애니메이션 GIF가 2MB를 넘지 않는 한 다른 이미지처럼이 사이트에 게시 할 수 있습니다.
rob mayoff

1
@LucaTorella 우선 순위가 높은 제약 조건 만 있으면 레이아웃이 모호합니다. 오늘 원하는 결과를 얻는다고해서 다음 버전의 iOS에서도 마찬가지라는 의미는 아닙니다. 서브 뷰가 종횡비 1 : 1을 원하고 슈퍼 뷰의 크기가 99 × 100이고 필요한 종횡비 및 우선 순위가 동일한 등가 제한 조건 만 있다고 가정하십시오. 그런 다음 자동 레이아웃은 서브 뷰를 크기 99 × 99 (총 오류 1) 또는 크기 100 × 100 (총 오류 1)으로 설정할 수 있습니다. 하나의 크기는 가로 세로입니다. 다른 하나는 측면 채우기입니다. 필요한 제약 조건을 추가하면 고유 한 최소 오류 솔루션이 생성됩니다.
rob mayoff

1
@LucaTorella 가로 세로 비율 제한에 대한 수정에 감사드립니다.
rob mayoff

11
하나의 게시물 만있는 10,000 개의 더 나은 앱 ...
DonVaughn

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

다음으로 스토리 보드의 파란색 및 분홍색보기 클래스를 AspectFitViews로 변경합니다. 마지막으로 당신은 당신의 ViewController에 두 개의 콘센트를 설정 topAspectFitView하고 bottomAspectFitView자신의 설정 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

1
프로그래밍 방식으로 참조하는 데 매우 유용합니다.
ctpenrose

튜토리얼 프로젝트가 있다면 @JohannesFahrenkrug를 공유 할 수 있습니까? 감사합니다 :)
Erhan Demirci

1
@ ErhanDemirci 물론, 여기 당신은 간다 : github.com/jfahrenkrug/SPWKAspectFitView
Johannes Fahrenkrug

8

허용 된 답변의 솔루션이 필요했지만 코드에서 실행되었습니다. 내가 찾은 가장 우아한 방법은 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);
}];

5

이것은 macOS 용입니다.

OS X 응용 프로그램에서 가로 세로로 맞추기 위해 Rob의 방법을 사용하는 데 문제가 있습니다. 그러나 다른 방법으로 만들었습니다. 너비와 높이를 사용하는 대신 선행, 후행, 상단 및 하단 공간을 사용했습니다.

기본적으로 하나는> = 0 @ 1000 필수 우선 순위이고 다른 하나는 = 0 @ 250 낮은 우선 순위 인 두 개의 선행 공백을 추가하십시오. 후행, 상단 및 하단 공간에 동일한 설정을 수행하십시오.

물론 종횡비와 중심 X 및 중심 Y를 설정해야합니다.

그리고 일이 끝났습니다!

여기에 이미지 설명을 입력하십시오 여기에 이미지 설명을 입력하십시오


프로그래밍 방식으로이 작업을 어떻게 수행 할 수 있습니까?
FateNuller

@FateNuller 그냥 단계를 수행?
brianLikeApple

4

나 자신 aspect- 역부족 채우기 있는 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.