Mô phỏng hành vi phù hợp với khía cạnh bằng các ràng buộc AutoLayout trong Xcode 6


336

Tôi muốn sử dụng AutoLayout để định cỡ và bố trí chế độ xem theo cách gợi nhớ đến chế độ nội dung phù hợp với khía cạnh của UIImageView.

Tôi có một khung nhìn bên trong khung nhìn container trong Interface Builder. Subview có một số tỷ lệ khía cạnh vốn có mà tôi muốn tôn trọng. Kích thước của khung nhìn container là không xác định cho đến khi thời gian chạy.

Nếu tỷ lệ khung hình của khung nhìn container rộng hơn so với khung nhìn phụ, thì tôi muốn chiều cao của khung nhìn phụ bằng với chiều cao của khung nhìn phụ.

Nếu tỷ lệ khung hình của khung nhìn container cao hơn so với khung nhìn phụ, thì tôi muốn chiều rộng của khung nhìn phụ bằng với chiều rộng của khung nhìn phụ.

Trong cả hai trường hợp, tôi muốn khung nhìn phụ được căn giữa theo chiều ngang và chiều dọc trong chế độ xem container.

Có cách nào để đạt được điều này bằng cách sử dụng các ràng buộc AutoLayout trong Xcode 6 hoặc trong phiên bản trước không? Lý tưởng nhất là sử dụng Trình tạo giao diện, nhưng nếu không có lẽ có thể xác định các ràng buộc đó theo lập trình.

Câu trả lời:


1046

Bạn không mô tả quy mô cho phù hợp; bạn đang mô tả phù hợp với khía cạnh. (Tôi đã chỉnh sửa câu hỏi của bạn về vấn đề này.) Cuộc phỏng vấn trở nên lớn nhất có thể trong khi duy trì tỷ lệ khung hình của nó và phù hợp hoàn toàn bên trong cha mẹ của nó.

Dù sao, bạn có thể làm điều này với bố trí tự động. Bạn có thể làm điều đó hoàn toàn bằng IB kể từ Xcode 5.1. Hãy bắt đầu với một số quan điểm:

một số quan điểm

Chế độ xem màu xanh nhạt có tỷ lệ khung hình là 4: 1. Chế độ xem màu xanh đậm có tỷ lệ khung hình là 1: 4. Tôi sẽ thiết lập các ràng buộc để chế độ xem màu xanh lấp đầy nửa trên của màn hình, chế độ xem màu hồng lấp đầy nửa dưới của màn hình và mỗi chế độ xem màu xanh lá cây mở rộng hết mức có thể trong khi duy trì tỷ lệ khung hình và khớp với nó thùng đựng hàng.

Đầu tiên, tôi sẽ tạo các ràng buộc ở cả bốn phía của chế độ xem màu xanh. Tôi sẽ ghim nó vào hàng xóm gần nhất của nó ở mỗi cạnh, với khoảng cách bằng 0. Tôi đảm bảo tắt lề:

hạn chế màu xanh

Lưu ý rằng tôi không cập nhật khung hình. Tôi thấy dễ dàng rời khỏi phòng giữa các chế độ xem khi thiết lập các ràng buộc và chỉ cần đặt các hằng số thành 0 (hoặc bất cứ thứ gì) bằng tay.

Tiếp theo, tôi ghim các cạnh trái, dưới và phải của chế độ xem màu hồng cho hàng xóm gần nhất của nó. Tôi không cần thiết lập ràng buộc cạnh trên vì cạnh trên của nó đã bị giới hạn ở cạnh dưới của chế độ xem màu xanh.

hạn chế màu hồng

Tôi cũng cần một ràng buộc về độ cao bằng nhau giữa các khung nhìn màu hồng và màu xanh. Điều này sẽ làm cho mỗi cái lấp đầy một nửa màn hình:

nhập mô tả hình ảnh ở đây

Nếu tôi bảo Xcode cập nhật tất cả các khung bây giờ, tôi sẽ nhận được điều này:

container đặt ra

Vì vậy, các ràng buộc tôi đã thiết lập cho đến nay là chính xác. Tôi hoàn tác điều đó và bắt đầu làm việc trên chế độ xem màu xanh nhạt.

Khung nhìn phù hợp với chế độ xem màu xanh nhạt yêu cầu năm ràng buộc:

  • Một ràng buộc tỷ lệ khung hình ưu tiên bắt buộc trên chế độ xem màu xanh nhạt. Bạn có thể tạo ràng buộc này trong xib hoặc bảng phân cảnh bằng Xcode 5.1 trở lên.
  • Ràng buộc ưu tiên bắt buộc giới hạn chiều rộng của chế độ xem màu lục nhạt nhỏ hơn hoặc bằng chiều rộng của vùng chứa.
  • Ràng buộc ưu tiên cao đặt chiều rộng của chế độ xem màu lục nhạt bằng với chiều rộng của vùng chứa.
  • Ràng buộc ưu tiên bắt buộc giới hạn chiều cao của chế độ xem màu lục nhạt nhỏ hơn hoặc bằng chiều cao của vùng chứa.
  • Ràng buộc ưu tiên cao đặt chiều cao của chế độ xem màu lục nhạt bằng với chiều cao của vùng chứa.

Hãy xem xét hai hạn chế chiều rộng. Các ràng buộc nhỏ hơn hoặc bằng nhau, tự nó, không đủ để xác định chiều rộng của chế độ xem màu xanh nhạt; nhiều chiều rộng sẽ phù hợp với các ràng buộc. Vì có sự không rõ ràng, tự động thanh toán sẽ cố gắng chọn một giải pháp giảm thiểu lỗi trong các ràng buộc khác (ưu tiên cao nhưng không bắt buộc). Giảm thiểu lỗi có nghĩa là làm cho chiều rộng càng gần càng tốt với chiều rộng của bộ chứa, trong khi không vi phạm các ràng buộc nhỏ hơn hoặc bằng nhau.

Điều tương tự xảy ra với các hạn chế chiều cao. Và vì cũng cần phải có ràng buộc tỷ lệ khung hình, nên nó chỉ có thể tối đa hóa kích thước của khung nhìn phụ dọc theo một trục (trừ khi vùng chứa xảy ra có cùng tỷ lệ khung hình với khung nhìn phụ).

Vì vậy, trước tiên tôi tạo ràng buộc tỷ lệ khung hình:

khía cạnh hàng đầu

Sau đó, tôi tạo các ràng buộc chiều rộng và chiều cao bằng nhau với container:

kích thước bằng nhau

Tôi cần chỉnh sửa các ràng buộc này thành các ràng buộc nhỏ hơn hoặc bằng:

đầu nhỏ hơn hoặc bằng kích thước

Tiếp theo tôi cần tạo một tập các ràng buộc chiều rộng và chiều cao bằng nhau với vùng chứa:

đầu bằng nhau một lần nữa

Và tôi cần làm cho những ràng buộc mới này ít hơn mức ưu tiên cần thiết:

đầu bằng không bắt buộc

Cuối cùng, bạn đã yêu cầu xem các khung nhìn phụ được đặt ở giữa trong thùng chứa của nó, vì vậy tôi sẽ thiết lập các ràng buộc đó:

trung tâm hàng đầu

Bây giờ, để kiểm tra, tôi sẽ chọn bộ điều khiển xem và yêu cầu Xcode cập nhật tất cả các khung. Đây là những gì tôi nhận được:

bố cục trên không chính xác

Giáo sư! Subview đã mở rộng để lấp đầy hoàn toàn container của nó. Nếu tôi chọn nó, tôi có thể thấy rằng trong thực tế nó duy trì tỉ lệ của nó, nhưng nó đang làm một aspect- điền thay vì một aspect- phù hợp .

Vấn đề là ở một ràng buộc nhỏ hơn hoặc bằng nhau, vấn đề là chế độ xem ở mỗi đầu của ràng buộc và Xcode đã thiết lập ràng buộc ngược với mong đợi của tôi. Tôi có thể chọn một trong hai ràng buộc và đảo ngược các mục đầu tiên và thứ hai của nó. Thay vào đó, tôi sẽ chỉ chọn chế độ xem phụ và thay đổi các ràng buộc thành lớn hơn hoặc bằng:

sửa các ràng buộc hàng đầu

Xcode cập nhật bố cục:

bố trí đầu đúng

Bây giờ tôi làm tất cả những điều tương tự với chế độ xem màu xanh đậm ở phía dưới. Tôi cần đảm bảo tỷ lệ khung hình của nó là 1: 4 (Xcode đã thay đổi kích thước theo cách kỳ lạ vì nó không có ràng buộc). Tôi sẽ không hiển thị các bước một lần nữa vì chúng giống nhau. Đây là kết quả:

bố trí trên và dưới đúng

Bây giờ tôi có thể chạy nó trong trình giả lập iPhone 4S, có kích thước màn hình khác với IB được sử dụng và xoay thử nghiệm:

kiểm tra iphone 4s

Và tôi có thể thử nghiệm trong trình giả lập iPhone 6:

kiểm tra iphone 6

Tôi đã tải lên bảng phân cảnh cuối cùng của mình lên ý chính này để thuận tiện cho bạn.


27
Tôi đã sử dụng LICEcap . Nó miễn phí (GPL) và ghi trực tiếp vào GIF. Miễn là GIF hoạt hình không quá 2 MB, bạn có thể đăng nó lên trang web này như bất kỳ hình ảnh nào khác.
cướp mayoff

1
@LucaTorella Chỉ với các ràng buộc ưu tiên cao, bố cục không rõ ràng. Chỉ vì nó đạt được kết quả mong muốn của bạn ngày hôm nay không có nghĩa là nó sẽ có trong phiên bản tiếp theo của iOS. Giả sử, khung nhìn phụ muốn tỷ lệ khung hình 1: 1, giám sát có kích thước 99 × 100 và bạn chỉ có tỷ lệ khung hình bắt buộc và các ràng buộc bình đẳng ưu tiên cao. Sau đó, bố cục tự động có thể đặt chế độ xem phụ thành kích thước 99 × 99 (với tổng lỗi 1) hoặc kích thước 100 × 100 (với tổng lỗi 1). Một kích thước là phù hợp với khía cạnh; khác là điền vào khía cạnh. Thêm các ràng buộc cần thiết tạo ra một giải pháp ít lỗi nhất.
cướp mayoff

1
@LucaTorella Cảm ơn bạn đã sửa lỗi về tính khả dụng của các ràng buộc tỷ lệ khung hình.
cướp mayoff

11
10.000 ứng dụng tốt hơn chỉ với một bài đăng ... không thể tin được.
DonVaughn

2
Wow .. Câu trả lời hay nhất ở đây trong stack overflow tôi từng thấy .. Cảm ơn bạn vì điều này thưa ngài ..
0yeoj

24

Rob, câu trả lời của bạn thật tuyệt vời! Tôi cũng biết rằng câu hỏi này đặc biệt về việc đạt được điều này bằng cách sử dụng bố cục tự động. Tuy nhiên, chỉ là một tài liệu tham khảo, tôi muốn chỉ ra làm thế nào điều này có thể được thực hiện trong mã. Bạn thiết lập chế độ xem trên cùng và dưới cùng (màu xanh và màu hồng) giống như Rob đã hiển thị. Sau đó, bạn tạo một tùy chỉnh 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

Tiếp theo, bạn thay đổi lớp của chế độ xem màu xanh và màu hồng trong bảng phân cảnh thành AspectFitViews. Cuối cùng, bạn đặt hai cửa hàng để viewController của bạn topAspectFitViewbottomAspectFitViewvà thiết lập của họ childViews ở 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;
}

Vì vậy, không khó để làm điều này trong mã và nó vẫn rất dễ thích nghi và hoạt động với các khung nhìn có kích thước thay đổi và các tỷ lệ khung hình khác nhau.

Cập nhật tháng 7 năm 2015 : Tìm một ứng dụng demo tại đây: https://github.com/jfahrenkrug/SPWKAspectFitView


1
@DanielGalasko Cảm ơn bạn, bạn đã đúng. Tôi đề cập đến điều đó trong câu trả lời của tôi. Tôi chỉ nghĩ rằng nó sẽ là một tài liệu tham khảo hữu ích để so sánh các bước liên quan đến cả hai giải pháp.
Julian Fahrenkrug

1
Rất hữu ích để có các tài liệu tham khảo chương trình là tốt.
ctpenrose

@JohannesFahrenkrug nếu bạn có dự án hướng dẫn, bạn có thể chia sẻ không? cảm ơn bạn :)
Erhan Demirci

1
@ErhanDemirci Chắc chắn, bạn vào đây: github.com/jfahrenkrug/SPWKAspectFitView
Johannes Fahrenkrug

8

Tôi cần một giải pháp từ câu trả lời được chấp nhận, nhưng được thực thi từ mã. Cách thanh lịch nhất mà tôi tìm thấy là sử dụng khung Masonry .

#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

Đây là cho macOS.

Tôi gặp vấn đề khi sử dụng cách của Rob để đạt được sự phù hợp về khía cạnh trên ứng dụng OS X. Nhưng tôi đã làm nó bằng một cách khác - Thay vì sử dụng chiều rộng và chiều cao, tôi đã sử dụng không gian hàng đầu, dấu, trên và dưới.

Về cơ bản, thêm hai không gian hàng đầu trong đó một không gian ưu tiên> = 0 @ 1000 và một không gian khác là = 0 @ 250 mức ưu tiên thấp. Thực hiện các cài đặt tương tự để theo dõi, không gian trên và dưới.

Tất nhiên, bạn cần đặt tỷ lệ khung hình và trung tâm X và trung tâm Y.

Và thế là xong việc!

nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây


Làm thế nào bạn có thể đi về làm điều này lập trình?
FateNuller

@FateNuller Chỉ cần làm theo các bước?
brianLikeApple

4

Tôi thấy mình muốn aspect- điền hành vi khía cạnh để UIImageView sẽ duy trì tỷ lệ khung hình của chính nó và hoàn toàn lấp đầy khung nhìn của container. Một cách khó hiểu, UIImageView của tôi đã phá vỡ cả hai ràng buộc độ rộng bằng nhau và chiều cao bằng nhau (được mô tả trong câu trả lời của Rob) và hiển thị ở độ phân giải đầy đủ.

Giải pháp đơn giản là đặt Ưu tiên kháng nén nội dung của UIImageView thấp hơn mức ưu tiên của các ràng buộc về chiều rộng và chiều cao bằng nhau:

Nội dung kháng nén


2

Đây là một câu trả lời tuyệt vời của @ rob_mayoff cho cách tiếp cận tập trung vào mã, sử dụng NSLayoutAnchorcác đối tượng và được chuyển đến Xamarin. Đối với tôi NSLayoutAnchorvà các lớp liên quan đã giúp chương trình AutoLayout dễ dàng hơn nhiều để lập trình:

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

Có lẽ đây là câu trả lời ngắn nhất, với Masonry , cũng hỗ trợ điền vào khía cạnh và kéo dài.

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);
        }
    }
}];
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.