ios - satisfiable - xcode unable to simultaneously satisfy constraints




Xcode 6에서 AutoLayout 제약 조건을 사용하여 애스펙트 피트 동작을 에뮬레이트 (5)

UIImageView가 자체 가로 세로 비율을 유지하고 컨테이너보기를 완전히 채울 수 있도록 가로 채우기 동작을 원했습니다. 혼란스럽게도, UIImageView는 높은 우선 순위의 등 폭 및 등 높이 제약 조건 (Rob의 답변에 설명되어 있음)과 렌더링을 최대 해상도로 모두 깨고있었습니다.

해결책은 간단하게 UIImageView의 내용 압축 저항 우선 순위를 등 폭 및 등 높이 제약 조건의 우선 순위보다 낮게 설정하는 것이 었습니다.

AutoLayout을 사용하여 UIImageView의 aspect-fit 컨텐트 모드를 연상시키는 방식으로 뷰의 크기와 레이아웃을 만들고 싶습니다.

인터페이스 빌더의 컨테이너보기 안에 하위보기가 있습니다. 하위보기에는 내가 존경하고 싶어하는 고유 한 종횡비가 있습니다. 컨테이너 뷰의 크기는 런타임까지 알 수 없습니다.

컨테이너보기의 종횡비가 하위보기보다 넓은 경우 하위보기의 높이를 상위보기의 높이와 같게하려고합니다.

컨테이너보기의 종횡비가 하위보기보다 크면 하위보기의 너비가 상위보기의 너비와 같아야합니다.

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

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


규모에 맞게 설명하는 것이 아닙니다. 당신은 aspect-fit을 묘사하고 있습니다. (이 점에 대해 나는 당신의 질문을 편집했다.) 서브 뷰는 종횡비를 유지하면서 완전히 부모 내부에 맞추어 가능한 한 커지게된다.

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

밝은 녹색보기의 종횡비는 4 : 1입니다. 짙은 녹색보기의 종횡비는 1 : 4입니다. 파란색보기가 화면의 위쪽 절반을 채우고, 분홍색보기가 화면의 아래쪽 절반을 채우고 각 녹색보기가 화면 비율을 유지하면서 가능한 한 많이 확장되도록 구속 조건을 설정하려고합니다. 컨테이너.

먼저, 파란색보기의 네면에 모두 제약 조건을 만듭니다. 각 가장자리의 가장 가까운 이웃에게 0의 거리로 고정시킵니다. 마진을 해제해야합니다.

아직 프레임을 업데이트 하지 않습니다 . 제약 조건을 설정할 때 뷰 사이에 공간을 두는 것이 더 쉬워졌으며 상수를 손으로 0 (또는 무엇이든)으로 설정했습니다.

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

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

Xcode가 지금 모든 프레임을 업데이트하라고 말하면,

이제까지 설정 한 제약 조건이 맞습니다. 나는 그것을 취소하고 연한 초록의 견해에 관한 연구를 시작한다.

밝은 녹색보기를 투영하려면 다섯 가지 제약 조건이 필요합니다.

  • 밝은 녹색보기에서 필수 우선 순위 종횡비 제약 조건입니다. 이 제약 조건은 Xcode 또는 Xcode 5.1 이상이 설치된 스토리 보드에서 만들 수 있습니다.
  • 밝은 녹색보기의 너비를 해당 컨테이너의 너비보다 작거나 같게 제한하는 필수 우선 순위 제약 조건입니다.
  • 밝은 녹색보기의 너비를 해당 컨테이너의 너비와 같게 설정하는 우선 순위 제약 조건입니다.
  • 밝은 녹색보기의 높이를 해당 컨테이너의 높이보다 작거나 같게 제한하는 필수 우선 순위 제약 조건입니다.
  • 밝은 녹색보기의 높이를 해당 컨테이너의 높이와 같게 설정하는 우선 순위 제약 조건입니다.

두 가지 너비 제약 조건을 고려해 보겠습니다. 동일하지 않은 제한 조건만으로는 밝은 녹색보기의 너비를 결정하기에 충분하지 않습니다. 많은 너비가 제약 조건에 부합합니다. 모호성이 있기 때문에, autolayout은 다른 (우선 순위는 있지만 필수는 아님) 제약 조건의 오류를 최소화하는 솔루션을 선택하려고합니다. 오류를 최소화하는 것은 필요한 너비 이하의 제약 조건을 위반하지 않으면 서 컨테이너의 너비에 최대한 가깝게 만드는 것을 의미합니다.

같은 문제가 높이 제약 조건에서 발생합니다. 또한 종횡비 제약 조건이 필요하기 때문에 한 축을 따라 하위 뷰의 크기를 최대화 할 수 있습니다 (컨테이너의 크기가 하위 뷰와 동일한 종횡비가 아닌 경우).

먼저 aspect ratio 제약 조건을 만듭니다.

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

이 제약 조건을 덜 또는 동일 제약 조건으로 편집해야합니다.

다음으로 컨테이너에 동일한 폭과 높이 제약 조건 집합을 새로 만들어야합니다.

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

마지막으로, 당신은 서브 뷰가 컨테이너의 중앙에 위치하도록 요청 했으므로, 다음과 같은 제약 조건을 설정할 것입니다.

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

죄송합니다. 하위 뷰가 확장되어 컨테이너를 완전히 채 웁니다. 선택하면 실제로 종횡비가 유지되지만 종횡비 대신 종횡비를 수행하는 것을 볼 있습니다.

문제는 덜 평등 한 제약 조건에서 제약 조건의 각 끝에있는보기가 중요하다는 것과 Xcode가 내 예상과 반대되는 제약 조건을 설정한다는 점입니다. 두 제약 조건 각각을 선택하고 첫 번째 및 두 번째 항목을 반대로 할 수 있습니다. 대신 하위 뷰를 선택하고 제약 조건을 크거나 같음으로 변경합니다.

Xcode에서 레이아웃을 업데이트합니다.

이제 나는 똑같은 일을 바닥에있는 짙은 초록빛으로 보았습니다. 종횡비가 1 : 4인지 확인해야합니다 (제약 조건이 없기 때문에 Xcode에서 이상한 방식으로 크기를 조정했습니다). 같은 단계이기 때문에 단계를 다시 표시하지 않겠습니다. 결과는 다음과 같습니다.

이제는 IB보다 다른 화면 크기를 가진 iPhone 4S 시뮬레이터에서 실행할 수 있으며 테스트 회전 :

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

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


어쩌면 이것은 Masonry 과 관련된 가장 짧은 대답 일 것입니다.

[containerView addSubview:subview];

[subview mas_makeConstraints:^(MASConstraintMaker *make) {
    if (contentMode == ContentMode_scaleToFill) {
        make.edges.equalTo(containerView);
    }
    else {
        make.center.equalTo(containerView);
        make.edges.equalTo(containerView).priorityHigh();
        make.width.equalTo(content.mas_height).multipliedBy(4.0 / 3);
        if (contentMode == ContentMode_scaleAspectFit) {
            make.width.height.lessThanOrEqualTo(containerView);
        }
        else { // contentMode == ContentMode_scaleAspectFill
            make.width.height.greaterThanOrEqualTo(containerView);
        }
    }
}];

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

롭, 당신의 대답은 굉장합니다! 또한이 질문은 특히 자동 레이아웃을 사용하여이 문제를 해결하는 것에 관한 것입니다. 그러나 참고 문헌과 같이 코드에서이 작업을 수행하는 방법을 보여 드리고자합니다. 롭이 보여준 것처럼 상단 및 하단보기 (파란색과 분홍색)를 설정합니다. 그런 다음 사용자 정의 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

다음으로 스토리 보드의 파란색 및 분홍색보기 클래스를 AspectFitViewAspectFitView 합니다. 마지막으로 viewcontroller topAspectFitViewbottomAspectFitView 두 개의 콘센트를 설정하고 viewDidLoad 에서 해당 childView 설정합니다.

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





autolayout