-->

Universal Autolayout for Scrollview & UI component

2019-09-04 07:14发布

问题:

I know auto layout but I am new to universal application with auto layout. I am having problem setting up Constrain to this below image.

Can you tell me how do I set auto layout for Universal App? I tried with constrain to margin & pin the leading,top,trailing space to container but it does not work.

回答1:

Actually, autolayout works different inside a scrollview than in other views. Inside a scroll view, the leading, trailing, top and bottom constraints form a superview to the container view (scrollview in this case) defines not spacing, but sort of "how much my scroll view should scroll to the left, right, top and bottom of this component".

So, to use autolayout in a scroll view, you must do some tricky things:

  1. Set leading, trailing, top and bottom constraints from your Scrollview to your view

  1. Add a subview to the scrollview, that you'll use as a guide view. Since scrollview width changes from device to device, you need a view that have a relationship with the width of the scrollview, so you can set your constraints related to that view instead of the scrollview's left and right borders (since this will define not the size, but "how much your scrollview scrolls to left or right"). This view have a height = 0, is located in 0,0, and have the same width as the scrollview.

  1. Set constraints for that view. Height = 0. Leading space to container, and Trailing space to container set to 0 (this tells your scrollview not to scroll to the sides of your guide view). Add a top constraint to the scrollview too, since your guide view will be at the top of the scrollview. Also add a Equal width relationship between your scrollview and your guide view (this is very important, it will force your guide view to have the same width as the scrollview, hence no horizontal scroll will be available). Note that at this point, your scrollview knows how much it must scroll to the left, right, and top, but not to the bottom, so you'll see an error in the autolayout.

  1. Now you build your UI, considering that the top, left and right relationships must be put in relationship to the guide view, NOT to the scrollview (you want to define spacing, not "amount of scroll"). It's hard to put all constraints needed here, so I created an example project on github: Download it here

When you open the project, please note that all constraints between an object and the scrollview doesn't reflect spacing (as I said) but reflects how much your scrollview should scroll away from the component.

Hope this helps,



回答2:

I don't know auto layout but the following code would work just as fine.. I much prefer constraints in code and its easier to manage, modify and understand.. I've tested the below on all device sizes..

Note: You don't have to put everything in ONE file.. Create different views and have each view handle their own constraints/layouts.

Portrait:

Landscape:

//
//  ViewController.m
//  test
//
//  Created by Brandon T on 2015-09-11.
//  Copyright (c) 2015 Brandon T. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UIView *paleometerView;
@property (nonatomic, strong) UIView *maleometerView;
@property (nonatomic, strong) UIView *influenceView;
@property (nonatomic, strong) UIButton *letCompanyKnowButton;
@property (nonatomic, strong) UIButton *shareWithFriendsButton;
@end

@implementation ViewController

- (instancetype)init {
    if (self = [super init]) {
        [self initControls];
    }
    return self;
}

/*- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        [self initControls];
    }
    return self;
}*/

/*- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
        [self initControls];
    }
    return self;
}*/

- (void)initControls {
    self.paleometerView = [[UIView alloc] init];
    self.maleometerView = [[UIView alloc] init];
    self.influenceView = [[UIView alloc] init];
    self.letCompanyKnowButton = [[UIButton alloc] init];
    self.shareWithFriendsButton = [[UIButton alloc] init];

    [self.paleometerView setBackgroundColor:[UIColor lightGrayColor]];
    [self.maleometerView setBackgroundColor:[UIColor grayColor]];

    [self.influenceView setBackgroundColor:[UIColor yellowColor]];
    [self.letCompanyKnowButton setBackgroundColor:[UIColor redColor]];
    [self.shareWithFriendsButton setBackgroundColor:[UIColor greenColor]];

    [self.view setBackgroundColor:[UIColor grayColor]];

    [self.letCompanyKnowButton setTitle:@"Let Company Know" forState:UIControlStateNormal];
    [self.shareWithFriendsButton setTitle:@"Share With Friends" forState:UIControlStateNormal];
}

- (void)viewDidLoad {
    [super viewDidLoad];


    [self initControls];

    [self layoutPaleMaleView];
    [self layoutInfluenceView];
    [self layoutAllViews];
}

- (void)layoutPaleMaleView {
    UILabel *paleLabel = [[UILabel alloc] init];
    UILabel *maleLabel = [[UILabel alloc] init];

    [paleLabel setText:@"Paleomenter\n85%\nPale"];
    [maleLabel setText:@"Maleometer\n71%\nMale"];

    [paleLabel setTextAlignment:NSTextAlignmentCenter];
    [maleLabel setTextAlignment:NSTextAlignmentCenter];

    [paleLabel setNumberOfLines:0];
    [maleLabel setNumberOfLines:0];

    [paleLabel setLineBreakMode:NSLineBreakByWordWrapping];
    [maleLabel setLineBreakMode:NSLineBreakByWordWrapping];

    [self.paleometerView addSubview:paleLabel];
    [self.maleometerView addSubview:maleLabel];

    [self.paleometerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[pale]-0-|" options:0 metrics:nil views:@{@"pale":paleLabel}]];
    [self.paleometerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[pale]-0-|" options:0 metrics:nil views:@{@"pale":paleLabel}]];

    [self.maleometerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[male]-0-|" options:0 metrics:nil views:@{@"male":maleLabel}]];
    [self.maleometerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[male]-0-|" options:0 metrics:nil views:@{@"male":maleLabel}]];

    [paleLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
    [maleLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
}

- (void)layoutInfluenceView {
    UIView *left = [[UIView alloc] init];
    UIView *middle = [[UIView alloc] init];
    UIView *right = [[UIView alloc] init];

    [left setBackgroundColor:[UIColor blueColor]];
    [middle setBackgroundColor:[UIColor yellowColor]];
    [right setBackgroundColor:[UIColor purpleColor]];

    [self.influenceView addSubview:left];
    [self.influenceView addSubview:middle];
    [self.influenceView addSubview:right];

    //Left, right and middle all have the same width.
    [self.influenceView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[left(==middle)]-0-[middle(==right)]-0-[right(==left)]-0-|" options:0 metrics:nil views:@{@"left":left, @"middle":middle, @"right":right}]];

    [self.influenceView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[left]-0-|" options:0 metrics:nil views:@{@"left":left}]];

    [self.influenceView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[middle]-0-|" options:0 metrics:nil views:@{@"middle":middle}]];

    [self.influenceView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[right]-0-|" options:0 metrics:nil views:@{@"right":right}]];

    [left setTranslatesAutoresizingMaskIntoConstraints:NO];
    [middle setTranslatesAutoresizingMaskIntoConstraints:NO];
    [right setTranslatesAutoresizingMaskIntoConstraints:NO];
}

- (void)layoutAllViews {
    [self.view addSubview:self.paleometerView];
    [self.view addSubview:self.maleometerView];
    [self.view addSubview:self.influenceView];
    [self.view addSubview:self.letCompanyKnowButton];
    [self.view addSubview:self.shareWithFriendsButton];


    NSDictionary *views = @{@"paleometer":self.paleometerView, @"maleometer":self.maleometerView, @"influence":self.influenceView, @"letCompanyKnow":self.letCompanyKnowButton, @"share":self.shareWithFriendsButton};

    NSMutableArray *constraints = [[NSMutableArray alloc] init];

    //Constrain *Horizontally* Paleometer and Maleometer to be equal widths, 0 spacing from the left and 0 spacing from the right, with 0 spacing between them..
    [constraints addObject:@"H:|-0-[paleometer(==maleometer)]-0-[maleometer(==paleometer)]-0-|"];

    //Constrain *Horizontally* InfluenceView to be 0 spacing from the left and 0 spacing from the right.
    [constraints addObject:@"H:|-0-[influence]-0-|"];

    //Constrain *Horizontally* the "letCompanyKnowButton" with 20 spacing on the left and 20 spacing on the right..
    [constraints addObject:[NSString stringWithFormat:@"H:|-%d-[letCompanyKnow]-%d-|", 20, 20]];

    //Constrain *Horizontally* the "shareButton" with 20 spacing on the left and 20 spacing on the right..
    [constraints addObject:[NSString stringWithFormat:@"H:|-%d-[share]-%d-|", 20, 20]];

    //Constrain *Vertically* the paleometer with 0 spacing from the top and 150 in height.
    //Below it we have 0 spacing and then the influenceView which has a minimum height of 250 but is flexible so it will size to fit whenever needed.
    //Next we have another 20 spacing below the influenceView and the letCompanyKnow button which has a height of 50 and has a 20 spacing above and below it.
    //Finally the shareWithFriends button has the same height as the letCompanyKnow button.
    [constraints addObject:@"V:|-0-[paleometer(150)]-0-[influence(250@500)]-20-[letCompanyKnow(50)]-20-[share(==letCompanyKnow)]-20-|"];

    //Constrain *Vertically* the maleometer to be equal height with paleometer.
    [constraints addObject:@"V:[maleometer(==paleometer)]"];

    //Make influenceView flexible height for both shrinking and expanding vertically.
    [self.influenceView setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical];
    [self.influenceView setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical];

    //add the constraints to the view.
    for (NSString *constraint in constraints) {
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:constraint options:0 metrics:nil views:views]];
    }

    for (UIView *view in self.view.subviews) {
        [view setTranslatesAutoresizingMaskIntoConstraints:NO];
    }
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end