Xcode 6'da AutoLayout kısıtlamalarını kullanarak boyuta sığma davranışını taklit etme


336

Bir görünümü UIImageView en boyuna uygun içerik modunu anımsatan bir şekilde boyut ve düzen için AutoLayout kullanmak istiyorum.

Arayüz Oluşturucu'da bir kapsayıcı görünümü içinde bir alt görünüm var. Alt görünümde saygı duymak istediğim doğal bir en-boy oranı var. Kapsayıcı görünümünün boyutu çalışma zamanına kadar bilinmiyor.

Kapsayıcı görünümünün en boy oranı alt görünümden daha genişse, alt görünümün yüksekliğinin üst görünümün yüksekliğine eşit olmasını istiyorum.

Kapsayıcı görünümünün en boy oranı alt görünümden daha uzunsa, alt görünümün genişliğinin üst görünümün genişliğine eşit olmasını istiyorum.

Her iki durumda da alt görünümün kapsayıcı görünümünde yatay ve dikey olarak ortalanmasını isterim.

Xcode 6'da veya önceki sürümde AutoLayout kısıtlamalarını kullanarak bunu başarmanın bir yolu var mı? İdeal olarak Arayüz Oluşturucu'yu kullanmak, ancak belki de böyle kısıtlamaları programlı olarak tanımlamak mümkündür.

Yanıtlar:


1046

Sığdırılacak ölçeği tanımlamıyorsunuz; en boy oranını tarif ediyorsunuz. (Sorunuzu bu bağlamda düzenledim.) Alt görünüm, en boy oranını korurken ve tamamen ebeveyninin içine sığdırırken mümkün olduğunca büyür.

Her neyse, bunu otomatik düzen ile yapabilirsiniz. Tamamen IB'de Xcode 5.1'den itibaren yapabilirsiniz. Bazı görünümlerle başlayalım:

bazı görüşler

Açık yeşil görünüm 4: 1 en boy oranına sahiptir. Koyu yeşil görünüm 1: 4 en boy oranına sahiptir. Kısıtlamalar ayarlayacağım, böylece mavi görünüm ekranın üst yarısını dolduracak, pembe görünüm ekranın alt yarısını dolduracak ve her yeşil görünüm en boy oranını koruyarak ve bir kap.

İlk olarak, mavi görünümün dört tarafında da kısıtlamalar oluşturacağım. Her kenardaki en yakın komşusuna 0 mesafeyle sabitleyeceğim. Kenar boşluklarını kapattığımdan emin olun:

mavi kısıtlamalar

Ben unutmayın yok henüz çerçeveyi güncelleyin. Kısıtlamaları ayarlarken görünümler arasında yer bırakmayı daha kolay buluyorum ve sadece sabitleri el ile 0 (ya da her neyse) olarak ayarladım.

Sonra, pembe görünümün sol, alt ve sağ kenarlarını en yakın komşusuna sabitliyorum. Üst kenarı zaten mavi görünümün alt kenarı ile sınırlı olduğu için bir üst kenar kısıtlaması ayarlamam gerekmiyor.

pembe kısıtlamalar

Ayrıca pembe ve mavi görünümler arasında eşit yükseklikte bir kısıtlamaya ihtiyacım var. Bu, her birinin ekranın yarısını doldurmasını sağlayacaktır:

resim açıklamasını buraya girin

Xcode'a şimdi tüm çerçeveleri güncellemesini söylersem, şunu elde ederim:

düzenlenmiş konteynerler

Yani şimdiye kadar kurduğum kısıtlamalar doğru. Bunu geri alıyorum ve açık yeşil manzara üzerinde çalışmaya başlıyorum.

Açık yeşil görünümü en boyuna sığdırmak beş kısıtlama gerektirir:

  • Açık yeşil görünümde zorunlu öncelikli en boy oranı kısıtlaması. Bu kısıtlamayı Xcode 5.1 veya sonraki bir sürümüyle bir xib'de veya film şeridinde oluşturabilirsiniz.
  • Açık yeşil görünümün genişliğini, kabının genişliğinden küçük veya ona eşit olacak şekilde sınırlayan gerekli öncelikli bir kısıtlama.
  • Açık yeşil görünümün genişliğini, kabının genişliğine eşit olacak şekilde ayarlayan yüksek öncelikli bir kısıtlama.
  • Açık yeşil görünümün yüksekliğini, kabının yüksekliğinden küçük veya ona eşit olacak şekilde sınırlayan gerekli öncelikli bir kısıtlama.
  • Açık yeşil görünümün yüksekliğini, kabının yüksekliğine eşit olarak ayarlayan yüksek öncelikli bir kısıtlama.

İki genişlik sınırlamasını ele alalım. Açık yeşil görünümün genişliğini belirlemek için tek başına eşit veya daha az kısıtlama yeterli değildir; birçok genişlik kısıtlamaya uyacaktır. Belirsizlik olduğu için, autolayout diğer (yüksek öncelikli ancak zorunlu olmayan) kısıtlamalarda hatayı en aza indiren bir çözüm seçmeye çalışacaktır. Hatayı en aza indirmek, gerekli veya eşit olmayan kısıtlamayı ihlal etmeden genişliği kabın genişliğine olabildiğince yakın yapmak anlamına gelir.

Aynı şey yükseklik kısıtı için de geçerlidir. En boy oranı kısıtlaması da gerekli olduğundan, yalnızca bir eksen boyunca alt görünümün boyutunu en üst düzeye çıkarabilir (kap alt görünümle aynı en boy oranına sahip olmadıkça).

İlk önce en boy oranı kısıtlaması oluşturuyorum:

en üst özellik

Sonra konteyner ile eşit genişlik ve yükseklik kısıtlamaları oluşturuyorum:

üst eşit boyut

Ben bu kısıtlamaları daha az veya eşit kısıtlamalar olması gerekir:

en küçük veya eşit büyüklükte

Sonra konteyner ile eşit genişlik ve yükseklik kısıtlamaları başka bir set oluşturmak gerekir:

tekrar eşit büyüklükte

Ve bu yeni kısıtlamaları gerekli öncelikten daha az yapmalıyım:

üst eşit gerekli değil

Son olarak, alt görünümün kapsayıcısında ortalanmasını istediniz, bu yüzden bu kısıtlamaları ayarlayacağım:

üst merkezli

Şimdi, test etmek için görünüm denetleyicisini seçeceğim ve Xcode'dan tüm çerçeveleri güncellemesini isteyeceğim. Bu ne olsun:

yanlış üst düzen

Hata! Alt görünüm kabını tamamen dolduracak şekilde genişledi. Bunu seçerseniz, ben aslında onun boy oranını muhafaza olduğunu görebilirsiniz, ancak bir aspect- yapıyor dolgu bir aspect- yerine oturması .

Sorun şudur ki, eşit veya daha az bir kısıtlamada, kısıtlamanın her iki ucunda hangi görünümün önemli olduğu ve Xcode beklentimden farklı bir kısıtlama getirmiştir. İki kısıtlamanın her birini seçebilir ve birinci ve ikinci öğelerini tersine çevirebilirim. Bunun yerine, yalnızca alt görünümü seçeceğim ve kısıtlamaları daha büyük veya eşit olacak şekilde değiştireceğim:

üst kısıtlamaları düzelt

Xcode düzeni günceller:

üst düzeni düzelt

Şimdi aynı şeyleri alttaki koyu yeşil görünüme yapıyorum. En boy oranının 1: 4 olduğundan emin olmalıyım (Xcode, kısıtlamaları olmadığı için garip bir şekilde yeniden boyutlandırıldı). Aynı olduklarından adımları tekrar göstermeyeceğim. İşte sonuç:

üst ve alt düzeni düzelt

Şimdi, kullanılan IB'den farklı bir ekran boyutuna sahip olan iPhone 4S simülatöründe çalıştırabilir ve rotasyonu test edebilirim:

iphone 4 s testi

Ve iPhone 6 simülatöründe test edebilirim:

iphone 6 testi

Size kolaylık olması için son storyboard'umu bu özete yükledim .


27
LICEcap kullandım . Ücretsizdir (GPL) ve doğrudan GIF'e kaydeder. Animasyonlu GIF en fazla 2 MB olduğu sürece, diğer sitelerde olduğu gibi bu siteye gönderebilirsiniz.
rob mayoff

1
@LucaTorella Yalnızca yüksek öncelikli kısıtlamalarla düzen belirsizdir. Bugün istediğiniz sonucu alması, iOS'un bir sonraki sürümünde olacağı anlamına gelmez. Alt görünümün en boy oranı 1: 1 istediğini, denetimin boyutunun 99 × 100 olduğunu ve yalnızca gerekli en boy oranına ve yüksek öncelikli eşitlik kısıtlamalarına sahip olduğunuzu varsayalım. Ardından otomatik düzen alt görünümü 99 × 99 (toplam hata 1 ile) veya 100 × 100 boyutuna (toplam hata 1 ile) ayarlayabilir. Bir boyut boyuna uygundur; diğeri ise en boy-dolgudur. Gerekli kısıtlamaları eklemek benzersiz bir en az hata çözümü üretir.
rob mayoff

1
@LucaTorella En boy oranı kısıtlamalarının kullanılabilirliğine ilişkin düzeltme için teşekkür ederiz.
rob mayoff

11
Tek bir gönderi ile 10.000 daha iyi uygulama ... inanılmaz.
DonVaughn

2
Vay .. Şimdiye kadar gördüğüm yığın taşması burada en iyi cevap .. Bu efendim için teşekkür ederim ..
0yeoj

24

Rob, cevabın harika! Bu sorunun özellikle otomatik düzen kullanarak bunu başarmakla ilgili olduğunu da biliyorum. Ancak, sadece bir referans olarak, bu kodda nasıl yapılabilir göstermek istiyorum. Rob'un gösterdiği gibi üst ve alt görünümleri (mavi ve pembe) ayarladınız. Sonra bir özel oluşturun 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

Ardından, film şeridindeki mavi ve pembe görünümlerin sınıfını AspectFitViews olarak değiştirin. Son olarak size viewcontroller iki çıkışları ayarlamak topAspectFitViewve bottomAspectFitViewve bunların set childViewiçinde s 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;
}

Bu yüzden bunu kodda yapmak zor değil ve hala çok uyarlanabilir ve değişken boyutlu görünümler ve farklı en boy oranları ile çalışıyor.

Temmuz 2015'i güncelleyin : Buradan bir demo uygulaması bulun: https://github.com/jfahrenkrug/SPWKAspectFitView


1
@DanielGalasko Teşekkür ederim, haklısın. Cevabımda bundan bahsediyorum. Her iki çözümde hangi adımların yer aldığını karşılaştırmanın yararlı bir referans olacağını düşündüm.
Johannes Fahrenkrug

1
Programlı referansa sahip olmak da çok faydalı.
ctpenrose

@JohannesFahrenkrug eğitim projeniz varsa paylaşır mısınız? teşekkür ederim :)
Erhan Demirci

1
@ErhanDemirci Tabii, hadi bakalım
Johannes Fahrenkrug

8

Kabul edilen cevaptan bir çözüme ihtiyacım vardı, ama koddan yürütüldüm. Bulduğum en zarif yol Duvarcılık çerçevesini kullanmak .

#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

Bu macOS içindir.

OS X uygulamasına uyum sağlamak için Rob'un yolunu kullanmakta sorun yaşıyorum. Ama başka bir yolla yaptım - genişlik ve yükseklik kullanmak yerine önde, arkada, üstte ve altta yer kullandım.

Temel olarak, biri> = 0 @ 1000 gerekli öncelik diğeri = 0 @ 250 düşük öncelik olmak üzere iki önde gelen boşluk ekleyin. İzleyen, üst ve alt boşluklarla aynı ayarları yapın.

Tabii ki, en boy oranını ve X merkezini ve Y merkezini ayarlamanız gerekir.

Ve sonra iş bitti!

resim açıklamasını buraya girin resim açıklamasını buraya girin


Bunu programlı olarak nasıl yapabilirsiniz?
FateNuller

@FateNuller Sadece adımları izliyor musunuz?
brianLikeApple

4

Kendimi bir UIImageView kendi en boy oranını korumak ve tamamen konteyner görünümü doldurmak istiyorum en boy dolgu davranışı isteyen bulundu . Kafa karıştırıcı bir şekilde, UIImageView cihazım yüksek öncelikli eşit genişlik ve eşit yükseklik kısıtlamalarını (Rob'un cevabında açıklanmıştır) kırıyor ve tam çözünürlükte oluşturuyordu.

Çözüm, UIImageView'ın İçerik Sıkıştırma Direnci Önceliğini eşit genişlik ve eşit yükseklik kısıtlamalarının önceliğinden daha düşük ayarlamaktı:

İçerik Sıkıştırma Direnci


2

Bu, @ rob_mayoff'un NSLayoutAnchornesneleri kullanan ve Xamarin'e taşınan , kod merkezli bir yaklaşıma mükemmel cevabı olan bir limandır. Benim için NSLayoutAnchorve ilgili sınıflar AutoLayout'u programlamayı çok daha kolaylaştırdı:

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

Belki de en-boy yanıtı ve esnemeyi destekleyen Masonry ile en kısa cevap budur .

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);
        }
    }
}];
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.