IT-Swarm.Net

Προσαρμογή συμπεριφοράς εμφάνισης με χρήση των περιορισμών AutoLayout στο Xcode 6

Θέλω να χρησιμοποιήσω το AutoLayout για το μέγεθος και τη διάταξη μιας προβολής με τρόπο που θυμίζει τη λειτουργία περιεχομένου UIImageView.

Έχω υποκλέψει μέσα σε μια προβολή κοντέινερ στο Interface Builder. Το subview έχει κάποια εγγενή αναλογία που θέλω να σεβαστώ. Το μέγεθος της προβολής του κοντέινερ είναι άγνωστο μέχρι την εκτέλεση.

Εάν ο λόγος πλευρών της προβολής δοχείου είναι ευρύτερος από το υποκείμενο, τότε θέλω το ύψος του υποσέλιδου να ισούται με το ύψος της γονικής προβολής.

Αν ο λόγος πλευρών της προβολής δοχείου είναι ψηλότερος από το υποκείμενο, τότε θέλω το πλάτος του υποσέλιδου να ισούται με το πλάτος της γονικής προβολής.

Σε κάθε περίπτωση, εύχομαι η υποενότητα να είναι κεντραρισμένη οριζόντια και κάθετα μέσα στην προβολή του δοχείου.

Υπάρχει τρόπος να επιτευχθεί αυτό χρησιμοποιώντας τους περιορισμούς AutoLayout στο Xcode 6 ή σε προηγούμενη έκδοση; Χρησιμοποιώντας ιδανικά το Interface Builder, αλλά αν όχι ίσως είναι δυνατόν να οριστούν αυτοί οι περιορισμοί προγραμματικά.

326
chris838

Δεν περιγράφετε κλίμακα για να ταιριάζει. περιγράφεις την εμφάνιση. (Έχω επεξεργαστεί την ερώτησή σας σε αυτό το θέμα.) Η υποβολή γίνεται όσο το δυνατόν μεγαλύτερη, διατηρώντας παράλληλα την αναλογία της και προσαρμόζοντας εξ ολοκλήρου μέσα στον γονέα της.

Τέλος πάντων, μπορείτε να το κάνετε αυτό με αυτόματη διάταξη. Μπορείτε να το κάνετε εξ ολοκλήρου στο IB από Xcode 5.1. Ας ξεκινήσουμε με μερικές προβολές:

some views

Η ανοιχτή πράσινη όψη έχει αναλογία διαστάσεων 4: 1. Η σκούρα πράσινη προβολή έχει αναλογία διαστάσεων 1: 4. Πρόκειται να δημιουργήσω περιορισμούς έτσι ώστε η μπλε όψη να γεμίζει το πάνω μισό της οθόνης, η ροζ όψη να γεμίζει το κάτω μισό της οθόνης και κάθε πράσινη προβολή να επεκτείνεται όσο το δυνατόν περισσότερο, διατηρώντας παράλληλα τον λόγο διαστάσεων και προσαρμόζοντας την δοχείο.

Κατ 'αρχάς, θα δημιουργήσω περιορισμούς και στις τέσσερις πλευρές της μπλε όψης. Θα το περάσω στον κοντινότερο γείτονα σε κάθε άκρη, με απόσταση 0. Φροντίζω να απενεργοποιήσω τα περιθώρια:

blue constraints

Σημειώστε ότι το I δεν ενημερώνει το πλαίσιο ακόμα. Θεωρώ ότι είναι ευκολότερο να αφήνετε χώρο μεταξύ των απόψεων κατά τη δημιουργία περιορισμών και απλά να ορίσετε τις σταθερές στο 0 (ή οτιδήποτε άλλο) με το χέρι.

Στη συνέχεια, κλίνω το αριστερό, το κάτω και το δεξί άκρο της ροζ εικόνας στον πλησιέστερο γείτονά της. Δεν χρειάζεται να ρυθμίσω έναν ανώτατο περιορισμό Edge επειδή η άνω άκρη του είναι ήδη περιορισμένη στο κάτω άκρο της μπλε όψης.

pink constraints

Χρειάζομαι επίσης έναν περιορισμό ισοδύναμων μεταξύ των ροζ και μπλε απόψεων. Αυτό θα τους κάνει να γεμίσουν το ήμισυ της οθόνης:

enter image description here

Αν λέω στο Xcode να ενημερώσει όλα τα πλαίσια τώρα, έχω αυτό:

containers laid out

Έτσι, οι περιορισμοί που έχω δημιουργήσει μέχρι τώρα είναι σωστοί. Ανεξάρτητα από αυτό και να αρχίσετε να εργάζεστε με την ανοιχτό πράσινη όψη.

Η τοποθέτηση σε πτυχές της ανοιχτής πράσινης προβολής απαιτεί πέντε περιορισμούς:

  • Απαιτούμενος λόγος αναλογίας διαστάσεων προτεραιότητας στην ανοιχτό πράσινη όψη. Μπορείτε να δημιουργήσετε αυτόν τον περιορισμό σε xib ή storyboard με Xcode 5.1 ή νεότερη έκδοση.
  • Ο περιορισμός της απαιτούμενης προτεραιότητας που περιορίζει το πλάτος της ανοιχτό πράσινης όψης να είναι μικρότερο ή ίσο με το πλάτος του δοχείου.
  • Ένας περιορισμός υψηλής προτεραιότητας που καθορίζει το πλάτος της ανοιχτής πράσινης όψης να είναι ίσο με το πλάτος του δοχείου.
  • Ο περιορισμός της απαιτούμενης προτεραιότητας που περιορίζει το ύψος της ανοιχτής πράσινης όψης να είναι μικρότερο ή ίσο με το ύψος του δοχείου.
  • Ένας περιορισμός υψηλής προτεραιότητας που θέτει το ύψος της ανοιχτής πράσινης όψης να είναι ίσο με το ύψος του δοχείου.

Ας εξετάσουμε τους δύο περιορισμούς πλάτους. Ο περιορισμός που δεν είναι ο ίδιος, είναι επαρκής για να καθορίσει το πλάτος της ανοιχτής πράσινης όψης. πολλά πλάτη θα ταιριάζουν με τον περιορισμό. Δεδομένου ότι υπάρχει ασάφεια, το autolayout θα προσπαθήσει να επιλέξει μια λύση που ελαχιστοποιεί το σφάλμα στον άλλο περιορισμό (υψηλής προτεραιότητας αλλά όχι υποχρεωτικό). Η ελαχιστοποίηση του σφάλματος σημαίνει ότι το πλάτος είναι όσο το δυνατόν πλησιέστερο στο πλάτος του κιβωτίου, ενώ δεν παραβιάζεται ο απαιτούμενος περιορισμός που δεν είναι μικρός ή ίσος.

Το ίδιο συμβαίνει και με τον περιορισμό του ύψους. Και επειδή απαιτείται επίσης ο περιορισμός της αναλογίας πλευρών, μπορεί να μεγιστοποιηθεί το μέγεθος του υποβιβασμού κατά μήκος ενός άξονα (εκτός αν ο περιέκτης έχει την ίδια αναλογία διαστάσεων με την υποέκθεση).

Έτσι πρώτα δημιουργώ τον περιορισμό της αναλογίας διαστάσεων:

top aspect

Στη συνέχεια, δημιουργώ ίσους περιορισμούς πλάτους και ύψους με το δοχείο:

top equal size

Πρέπει να επεξεργαστώ αυτούς τους περιορισμούς ώστε να είναι λιγότερο από-ή-ίσους περιορισμούς:

top less than or equal size

Στη συνέχεια πρέπει να δημιουργήσω ένα άλλο σύνολο ίσων περιορισμών πλάτους και ύψους με το δοχείο:

top equal size again

Και πρέπει να θέσω τους νέους αυτούς περιορισμούς λιγότερο από την απαιτούμενη προτεραιότητα:

top equal not required

Τελικά, ζητήσατε να είναι κεντραρισμένο το υποπεριοχή στο δοχείο του, οπότε θα ρυθμίσω αυτούς τους περιορισμούς:

top centered

Τώρα, για να δοκιμάσετε, θα επιλέξω τον ελεγκτή προβολής και θα ζητήσω από το Xcode να ενημερώσει όλα τα πλαίσια. Αυτό έχω:

incorrect top layout

Ωχ! Το subview έχει επεκταθεί για να γεμίσει πλήρως το δοχείο του. Αν το επιλέξω, μπορώ να δω ότι στην πραγματικότητα διατηρείται η αναλογία του, αλλά κάνει μια πτυχή - συμπληρώστε αντί για μια πτυχή - ταιριάζει .

Το πρόβλημα είναι ότι σε έναν λιγότερο από ή ίσο περιορισμό, έχει σημασία ποια είναι η άποψη σε κάθε άκρο του περιορισμού, και το Xcode έχει δημιουργήσει τον περιορισμό απέναντι από την προσδοκία μου. Θα μπορούσα να επιλέξω καθέναν από τους δύο περιορισμούς και να αντιστρέψω το πρώτο και δεύτερο στοιχείο. Αντ 'αυτού, θα επιλέξω μόνο το subview και θα αλλάξω τους περιορισμούς ώστε να είναι μεγαλύτερο από-ή-ίσο:

fix top constraints

Το Xcode ενημερώνει τη διάταξη:

correct top layout

Τώρα κάνω όλα τα ίδια πράγματα με τη σκούρα πράσινη θέα στο κάτω μέρος. Πρέπει να σιγουρευτώ ότι η αναλογία πλευρών είναι 1: 4 (το Xcode το μείωσε με περίεργο τρόπο αφού δεν είχε περιορισμούς). Δεν θα δείξω τα βήματα ξανά, αφού είναι τα ίδια. Εδώ είναι το αποτέλεσμα:

correct top and bottom layout

Τώρα μπορώ να το εκτελέσω στον προσομοιωτή iPhone 4S, ο οποίος έχει διαφορετικό μέγεθος οθόνης από τον IB που χρησιμοποιείται και δοκιμή περιστροφής:

iphone 4s test

Και μπορώ να δοκιμάσω στο iPhone 6 προσομοιωτή:

iphone 6 test

Έχω ανεβάσει τον τελικό πίνακα ιστορικού μου στο αυτό το Gist για την ευκολία σας.

1034
rob mayoff

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

Στη συνέχεια, αλλάζετε την κλάση των μπλε και ροζ απόψεων στο storyboard για να είναι AspectFitViews. Τέλος, ορίσατε δύο έξοδοι στον ελεγκτή topAspectFitView και bottomAspectFitView και ορίσατε το 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;
}

Επομένως, δεν είναι δύσκολο να το κάνετε αυτό στον κώδικα και εξακολουθεί να είναι πολύ προσαρμόσιμο και λειτουργεί με μεταβλητού μεγέθους προβολές και διαφορετικούς λόγους αναλογίας.

Ενημέρωση Ιουλίου 2015 : Βρείτε μια εφαρμογή επίδειξης εδώ: https://github.com/jfahrenkrug/SPWKAspectFitView

24
Johannes Fahrenkrug

Χρειαζόμουν μια λύση από την αποδεκτή απάντηση, αλλά εκτελείται από τον κώδικα. Ο πιο κομψός τρόπος που έχω βρει χρησιμοποιεί Πλαίσιο τοιχοποιίας .

#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);
}];
7
Tomasz Bąk

Αυτό είναι για το macOS

Έχω πρόβλημα να χρησιμοποιήσω τον τρόπο του Rob για να φτάσω στην εφαρμογή OS X. Αλλά το έκανα με άλλο τρόπο. Αντί να χρησιμοποιώ πλάτος και ύψος, χρησιμοποίησα το κορυφαίο, το τελικό, το επάνω και το κάτω μέρος.

Βασικά, προσθέστε δύο κύριους χώρους όπου η μία είναι> = 0 @ 1000 απαιτούμενη προτεραιότητα και άλλη μία είναι = 0 @ 250 χαμηλής προτεραιότητας. Πραγματοποιήστε τις ίδιες ρυθμίσεις στο τελικό, επάνω και κάτω κενό.

Φυσικά, πρέπει να ρυθμίσετε το λόγο διαστάσεων και το κέντρο X και το κέντρο Y.

Και έπειτα δουλειά!

enter image description hereenter image description here

4
brianLikeApple

Βρήκα τον εαυτό μου να θέλει την εμφάνιση -fill συμπεριφορά έτσι ώστε ένα UIImageView να διατηρεί το δικό του λόγο διαστάσεων και να γεμίζει πλήρως την προβολή του δοχείου. Με σύγχυση, το UIImageView μου έσπασε δύο βασικούς περιορισμούς ίσου πλάτους και ίσου ύψους (περιγράφονται στην απάντηση του Rob) και απόδοση σε πλήρη ανάλυση.

Η λύση ήταν απλά να ορίσετε την προτεραιότητα αντοχής συμπίεσης περιεχομένου UIImageView χαμηλότερη από την προτεραιότητα των περιορισμών ίσου πλάτους και ίσου ύψους:

Content Compression Resistance

3
Gregor

Πρόκειται για μια εξαιρετική απάντηση της @ rob_mayoff στην κωδικοκεντρική προσέγγιση, χρησιμοποιώντας αντικείμενα NSLayoutAnchor και μεταφερόμενα στο Xamarin. Για μένα, το 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
Larry OBrien

Ίσως αυτή να είναι η συντομότερη απάντηση, με Τοιχοποιία , η οποία υποστηρίζει επίσης την εμφάνιση και την τέντωμα.

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);
        }
    }
}];
1
iwill