Auto layout visual programming language for an array of views - ios

Let's say I have an array of views, and I want to stack these views in a list. Now, if I know ahead of time how many views there are, I could write a constraint like this:
"V:|-[view0]-[view1]-[view2]-[view_n]"
However, how can I accomplish something like this with a variable number of views in my array?

You can iterate over the array and build the string (use an NSMutableString). You need to add the views to a dictionary with keys that match the names you use in the format string.

Check out this awesome category:
https://github.com/jrturton/UIView-Autolayout
It has a spaceViews method that you can call on the container view and will space an array of views evenly along the specified axis.
There's some sample code in the demo project that should cover everything.
Here is how you would space some views evenly over the vertical axis:
This centre 4 views on the x axis and constrain the width to 150 points. The height would then be calculated depending on the height of self.view
#import "UIView+AutoLayout.h"
...
- (void)spaceViews
{
NSArray *views = #[ [self spacedView], [self spacedView], [self spacedView], [self spacedView] ];
[self.view spaceViews:views onAxis:UILayoutConstraintAxisVertical withSpacing:10 alignmentOptions:0];
}
- (UIView *)spacedView
{
//Create an autolayout view
UIView *view = [UIView autoLayoutView];
//Set the backgroundColor
[view setBackgroundColor:[UIColor redColor]];
//Add the view to the superview
[self.view addSubview:view];
//Constrain the width and center on the x axis
[view constrainToSize:CGSizeMake(150, 0)];
[view centerInContainerOnAxis:NSLayoutAttributeCenterX];
//Return the view
return view;
}

I had a requirement to add views from an array to a scrollview for my tutorial pages, in this case I built the VFL string by looping through the views, below is a snapshot, this code is to fit subview fully into scrollview's page. With some tweaks, padding,etc can be added. Anyways posting it here so that it helps someone.
Full code arrayAutolayout
/*!
Create an array of views that we need to load
#param nil
#result creates array of views and adds it to scrollview
*/
-(void)setUpViews
{
[viewsDict setObject:contentScrollView forKey:#"parent"];
int count = 20;//Lets layout 20 views
for (int i=0; i<=count; i++) {
// I am loading the view from xib.
ContentView *contenView = [[NSBundle mainBundle] loadNibNamed:#"ContentView" owner:self options:nil][0];
contenView.translatesAutoresizingMaskIntoConstraints = NO;
// Layout the text and color
[contenView layoutTheLabel];
[contentScrollView addSubview:contenView];
[viewsArray addObject:contenView];
}
}
/*!
Method to layout the childviews in the scrollview.
#param nil
#result layout the child views
*/
-(void)layoutViews
{
NSMutableString *horizontalString = [NSMutableString string];
// Keep the start of the horizontal constraint
[horizontalString appendString:#"H:|"];
for (int i=0; i<viewsArray.count; i++) {
// Here I am providing the index of the array as the view name key in the dictionary
[viewsDict setObject:viewsArray[i] forKey:[NSString stringWithFormat:#"v%d",i]];
// Since we are having only one view vertically, then we need to add the constraint now itself. Since we need to have fullscreen, we are giving height equal to the superview.
NSString *verticalString = [NSString stringWithFormat:#"V:|[%#(==parent)]|", [NSString stringWithFormat:#"v%d",i]];
// add the constraint
[contentScrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:verticalString options:0 metrics:nil views:viewsDict]];
// Since we need to horizontally arrange, we construct a string, with all the views in array looped and here also we have fullwidth of superview.
[horizontalString appendString:[NSString stringWithFormat:#"[%#(==parent)]", [NSString stringWithFormat:#"v%d",i]]];
}
// Close the string with the parent
[horizontalString appendString:#"|"];
// apply the constraint
[contentScrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:horizontalString options:0 metrics:nil views:viewsDict]];
}
Below is the string created
H:|[v0(==parent)][v1(==parent)][v2(==parent)][v3(==parent)][v4(==parent)][v5(==parent)][v6(==parent)][v7(==parent)][v8(==parent)][v9(==parent)][v10(==parent)][v11(==parent)][v12(==parent)][v13(==parent)][v14(==parent)][v15(==parent)][v16(==parent)][v17(==parent)][v18(==parent)][v19(==parent)][v20(==parent)]|

Related

Space evenly across a view without margins

I use following function to space a dynamic number of views across a View (with autolayout):
-(NSArray*)spaceViews:(NSArray *)views onAxis:(UILayoutConstraintAxis)axis
{
NSAssert([views count] > 1,#"Can only distribute 2 or more views");
NSLayoutAttribute attributeForView;
NSLayoutAttribute attributeToPin;
switch (axis) {
case UILayoutConstraintAxisHorizontal:
attributeForView = NSLayoutAttributeCenterX;
attributeToPin = NSLayoutAttributeRight;
break;
case UILayoutConstraintAxisVertical:
attributeForView = NSLayoutAttributeCenterY;
attributeToPin = NSLayoutAttributeBottom;
break;
default:
return #[];
}
CGFloat fractionPerView = 1.0 / (CGFloat)([views count] + 1);
NSMutableArray *constraints = [NSMutableArray array];
[views enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop)
{
CGFloat multiplier = fractionPerView * (idx + 1.0);
[constraints addObject:[NSLayoutConstraint constraintWithItem:view
attribute:attributeForView
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:attributeToPin
multiplier:multiplier
constant:0.0]];
}];
[self addConstraints:constraints];
return [constraints copy];
}
(The function is from UIView-Autolayout)
The problem is that it has margins. How could this method be changed in order to evenly space the views without margins?
UPDATE
It seems that the method is not working correctly after all, the margins on the outer items are much higher than at the rest.
As an example I changed the code for the left arm to:
self.leftArm = [UIView autoLayoutView];
self.leftArm.backgroundColor = [UIColor grayColor];
[self.view addSubview:self.leftArm];
[self.leftArm pinAttribute:NSLayoutAttributeRight toAttribute:NSLayoutAttributeLeft ofItem:self.body withConstant:-10.0];
[self.leftArm pinAttribute:NSLayoutAttributeTop toSameAttributeOfItem:self.body withConstant:10.0];
[self.leftArm constrainToSize:CGSizeMake(20.0, 200.0)];
Now the robot looks like this:
Note that at the left arm the views are NOT evenly distributed, the margin on top and at the bottom are much higher. I can reproduce the same behavior horizontally and with less items.
I have successfully removed the margins by aligning the outer views with the sides of the superview and aligning the rest within the space between. But now, due to this problem, I always have bigger spaces between the outer views and the views next to them as between the rest.
Does anyone know how to correct this?
UPDATE 2
I have created a fork and added my function to it, I also extended the robot example so you can see what I mean.
Please take a look.
It looks as though this code is from UIView-Autolayout. The latest version of the library has a new method which lets you stop the spacing from being added to edges:
-(NSArray*)spaceViews:(NSArray*)views
onAxis:(UILayoutConstraintAxis)axis
withSpacing:(CGFloat)spacing
alignmentOptions:(NSLayoutFormatOptions)options
flexibleFirstItem:(BOOL)flexibleFirstItem
applySpacingToEdges:(BOOL)spaceEdges;
Just call this method instead and pass NO to the last argument.
I have extended jrturton's UIView-Autolayout (based on this answer) and created a pull request.
It is now possible to distribute the views evenly without margins by calling the new function:
-(NSArray*)spaceViews:(NSArray *)views
onAxis:(UILayoutConstraintAxis)axis
withMargin:(BOOL)margin
and setting the margin parameter to NO.
You can already find that version of UIView-Autolayout here.
The brand new robot with evenly spaced views as teeth:

iOS autolayout - how to bind label's positions with particular places of a background image?

I'm working on an application that will have a picture of guitar fretboard like on a screenshot above. There will be notes displayed in different places of fretboard (represented by red circles- there will be much more of them than on the screenshot).
What kind of solution would you recommend to guarantee that the notes will be displayed in the right places of fretboard (which is just an image) and will not fall apart or distribute unevenly? Remember that the fretboard image will scale, depending on resolution, so notes positions coordinates should change accordingly.
If you're going to use an image that scales with the screen size, then this is one of the few cases where I would probably not use auto layout. I would create an array of doubles that would be the fraction of the distance from the left edge to the center of a particular space between the frets. So for instance, the value at index 3 (for the space where your left most red dot is) would be 0.2707 (36mm/133mm based on your image). You would then set the frame's origin.x value with that fraction times the width of the image.
You want to do all this in code I think and be able to activate based on fret location, string location etc... Use relative offsets for each string and fret from a known point.
This ties into your other question on SO about getting your fret image correct. Unless you can code the image accurately, then coding the note positions accurately is going to be tricky.
IMHO, you can not do this with auto layout, esp taking into account your other question: iOS autolayout - gap between image and top of the screen, despite constraints set
If you already managed to place the imageViews in the right places, you could create an UIView subclass that contains both the image and a label on top of it.
This would be my suggestion:
#interface NoteView()
#property (nonatomic, weak) UILabel *label;
#property (nonatomic, weak) UIImageView *imageView;
#end
#implementation NoteView
-(instancetype)init {
self = [super init];
if (self) {
[self setupContent];
}
return self;
}
- (void)setupContent {
UIImageView *imageView = [[UIImageView alloc] init];
[self addSubview:imageView];
[imageView setTranslatesAutoresizingMaskIntoConstraints:NO];
UILabel *label = [[UILabel alloc] init];
[self addSubview:label];
[label setTranslatesAutoresizingMaskIntoConstraints:NO];
// This adds vertical constaints between the view, the label and the imageView
// You can change the values to suit your needds
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[label]-10-[imageView]-0-|" options:0 metrics:nil views:#{ #"label": label, #"imageView": imageView }]];
// This makes the view be as big as the label
// I assumed the labels will be bigger than the images,
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[label]-0-|" options:0 metrics:nil views:#{ #"label": label}]];
// If the images are bigger use these constraints
// [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[label]-0-|" options:0 metrics:nil views:#{ #"label": label}]];
// This msakes sure that the label and the image have the same centers
[self addConstraint:[NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:imageView attribute:NSLayoutAttributeCenterX multiplier:1.f constant:0]];
[self setLabel:label];
[self setImageView:imageView];
}
// This would be a public configuration method.
- (void)setLabelText:(NSString *)text andImage:(UIImage *)image {
[self.label setText:text];
[self.imageView setImage:image];
}
#end
All you would need to do is place this custom view as you do with the images and if the frame changes call layoutIfNeeded on the custom view so that it layouts both the image and the label correctly.
Hope this helps. Let me know if you have questions.

iOS7 Autolayout and UIImageview oversized

Please see my answer below, where I have a working solution
I have rewritten the code completely so it is much smaller and neater and achieves the same results.
Update: It looks like the intrinsic content size of the UIImageView is being adjusted when the large image is loaded, which throws its layout and makes it twice the width of device window and scrollview. I will need to figure out how to fix that. The code below made a small change and reduced the width of the UIImageView, but did not allow me to set it to be 100% the width of the window.
[captionImageView setContentCompressionResistancePriority:1 forAxis:UILayoutConstraintAxisHorizontal];
I have the following set up for my views and my problem is that an image inside the UIImageView is pushing the width of its containing view to be wider than the device window.
I have a ViewController that loads and adds subviews in a for loop and everything seems to be working ok there.
Inside these sub views I am trying to use autolayout to achieve the following:
Add a UIImageView and make it the same width as the parent window
Load a UIImage into this and use UIViewContentModeScaleAspectFill to make sure the image loads nice and proportionally.
Load a UITextView beneath all this
Repeat this process in the for loop so they all stack up nicely on top of one another.
I am using a constraint for in the Visual formatting language to try to set the size of the UIImage view to be the width of the parent view, which I want to be the device window width.
If I specify the constraint as follows:
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[captionImageView(320)]|" options:0 metrics:metrics views:views]];
Then it works ok, but this is not the ideal solution as I want the UIImageView to be flush with the width of the device, and it's bad practice to set a fixed width
If I specify the constraint as follows like this:
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[captionImageView]|" options:0 metrics:metrics views:views]];
Then the UIImage with a size of let's say 960px will force the parent container view to be that size as well.
I have my custom view content being loaded into a scroll view, and so we have horizontal scrolling which is bad.
Note that I am using a UIView category to specify that view.translatesAutoresizingMaskIntoConstraints is set to NO.
#import "UIView+Autolayout.h"
#implementation UIView (Autolayout)
+(id)autoLayoutView {
UIView *view = [self new];
view.translatesAutoresizingMaskIntoConstraints = NO;
return view;
}
#end
The code for the custom view is below. Any ideas as to how I can ensure the size of my UIImageView does not stretch beyond the bounds of the device window?
//
// FigCaptionView.m
//
//
// Created by Matthew Finucane on 18/12/2014.
// Copyright (c) 2014 The App. All rights reserved.
//
#import "FigCaptionView.h"
#import "UIView+Autolayout.h"
#interface FigCaptionView(){}
#end
#implementation FigCaptionView
-(id)initWithData:(NSDictionary *)viewData {
if((self = [FigCaptionView autoLayoutView])) {
self.figCaptionData = viewData;
}
return self;
}
-(void)layoutItems {
[self setBackgroundColor:[UIColor redColor]];
/**
* The container view for everything
*/
UIView *containerView = [UIView autoLayoutView];
/**
* Setting up the caption image
*/
UIImageView *captionImageView = [UIImageView autoLayoutView];
[captionImageView setContentMode:UIViewContentModeScaleAspectFill];
/**
* Grabbing the image (not yet the right way to do this)
*/
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://placekitten.com/960/240"] options:0 error:nil]];
dispatch_async(dispatch_get_main_queue(), ^{
captionImageView.image = image;
});
});
/**
* Setting up the caption image
*/
UITextView *captionTextView = [UITextView autoLayoutView];
[captionTextView setText:#"Sample paragraph text will go in here. Sample paragraph text will go in here. Sample paragraph text will go in here. Sample paragraph text will go in here. Sample paragraph text will go in here.Sample paragraph text will go in here"];
/**
* Adding the container view
*/
[self addSubview:containerView];
[containerView addSubview:captionImageView];
[containerView addSubview:captionTextView];
[captionTextView setBackgroundColor:[UIColor blueColor]];
/**
* Dictionaries for autolayout: views and metrics
*/
NSDictionary *views = NSDictionaryOfVariableBindings(containerView, captionImageView, captionTextView);
NSDictionary *metrics = #{#"imageHeight": #"160.0", #"margin": #"20.0"};
/**
* Setting up the constraints for this view
*/
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[containerView]|" options:0 metrics:metrics views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[containerView]|" options:0 metrics:metrics views:views]];
/**
* Container view constraints
*
* The first constraint is the one that might be causing me trouble.
*/
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[captionImageView]|" options:0 metrics:metrics views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[captionTextView]|" options:0 metrics:metrics views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[captionImageView(imageHeight)][captionTextView]|" options:NSLayoutFormatAlignAllLeft metrics:metrics views:views]];
for(UIView *view in self.subviews) {
if([view hasAmbiguousLayout]) {
NSLog(#"OOPS");
NSLog(#"<%#:0x%0x>", view.description, (int)self);
}
}
}
#end
You can handle it in this way.
Assume ViewA's width is equal to the device window 's width.In this case, you can just add a UIView.
And set it up with constraints likes the usual view you had handled above.
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[captionImageView(ViewA)]|" options:0 metrics:metrics views:views]];
Hope this help.
Edit:
Cause I am not very sure it will work or not in the way that you comment to me. So I just showed the code how I deal with.
1.set up the viewA
_viewA = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
2.Just handle it likes your UIImageView (in this case, captionImageView)
[self.view addSubView:_viewA];
[_viewA setTranslatesAutoresizingMaskIntoConstraints:NO];
3.Add it into NSDictionaryBinding
NSDictionary *dictionary = NSDictionaryOfVariableBindings(_textView, _button, _viewA);
4.Custom the VFL and assign it.
NSString *const KButtonHorizontal = #"|[_button(_viewA)]|";
This is the tech note that explains how to constrain a scroll view: https://developer.apple.com/library/ios/technotes/tn2154/_index.html
A quick and easy way to prevent the scroll view content size from growing is to subclass the scrollview and override -(CGSize)contentSize to return the windows width and whatever super returns for height.
I got this working by Subclassing UIImageView and overriding intrinsicContentSize() to return the screen size... less code ;)
class CustomImageView: UIImageView
{
// - MARK: UIView
override func intrinsicContentSize() -> CGSize
{
// Return current screen size with width adjusted for frame
let width = UIScreen.mainScreen().bounds.width - frame.origin.x
return CGSize(width: width, height: frame.height)
}
}
I would not necessarily call this a definitive answer, but I was able to get it working somewhat the way I wanted to, although the solution I went with might not have been 100%.
-- The answer provided by Henry T Kirk was very useful in describing autolayout with image views. I could not get this working 100% but I would say this is the most correct answer in that it follows best practice.
-- The answer provided by WorldOfWarcraft also works. Creating a new view that stretches to 100% the width of the device window, and setting the constraint of the UIImage based on that will stop it from stretching beyond its bounds.
The solution I went with was to create a new CGRect variable called windowBounds. Extracting the window width from that (windowBounds.size.width), I was able to add this to my metrics, and apply it to the visual formatting string horizontal rule for the imageView, thus correcting the constraint for its superview.
After doing some work to refactor the code for this view, I have come up with a much neater solution that works, and also solves another issue I had with placing a UITextView with variable string content. The code is below.
** FigcaptionView.h **
//
// FigCaptionView.m
// Anna Christoffer
//
// Created by Matthew Finucane on 18/12/2014.
// Copyright (c) 2014 Anna Christoffer. All rights reserved.
//
#import "FigCaptionView.h"
#import "UIView+Autolayout.h"
#interface FigCaptionView(){}
#property (nonatomic, strong) UIImageView *figCaptionImageView;
#property (nonatomic, strong) UITextView *figCaptionTextView;
#end
#implementation FigCaptionView
#synthesize figCaptionImageView;
#synthesize figCaptionTextView;
-(id)initWithData:(NSDictionary *)viewData {
if((self = [FigCaptionView autoLayoutView])) {
self.figCaptionData = viewData;
}
return self;
}
-(void)loadImage:(NSString *)imageURLPath {
//TODO
}
-(void)addContentViews {
[super layoutSubviews];
/**
* Set up and add the subviews to display the content
*/
self.figCaptionImageView = [UIImageView autoLayoutView];
[self.figCaptionImageView setBackgroundColor:[UIColor lightGrayColor]];
[self addSubview:figCaptionImageView];
self.figCaptionTextView = [UITextView autoLayoutView];
[self.figCaptionTextView setScrollEnabled:NO];
[self.figCaptionTextView setText:#"The digital atlas teaches the cartographic and cultural contents with a highly dynamic and visual method. The idea is based on the phenomenon of “cabinets of wonder” from the 16th and 17th century. At this time European discoverers collected during their expeditions various exotic objects and on the turn to Europe replaced the found pieces to a universal collection."];
[self addSubview:figCaptionTextView];
/**
* Then apply the constraints
*/
[self autoLayoutAddConstraints];
}
-(void)autoLayoutAddConstraints {
/**
* Any set up values that we need
*/
CGRect windowBounds = [[UIScreen mainScreen] bounds];
/**
* Dictionary of views and metrics
*/
NSDictionary *views = NSDictionaryOfVariableBindings(self, figCaptionImageView, figCaptionTextView);
NSDictionary *metrics = #{#"imageWidth": #(windowBounds.size.width), #"margin": #20.0f};
/**
* Constraints for this view (Vertical and horizontal)
*/
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[figCaptionImageView(imageWidth)]|"
options:0
metrics:metrics
views:views
]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[figCaptionImageView][figCaptionTextView]|"
options:0
metrics:metrics
views:views
]];
/**
* Constraints for the caption image view to maintain ratio
*/
[self.figCaptionImageView addConstraint:[NSLayoutConstraint constraintWithItem:self.figCaptionImageView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.figCaptionImageView
attribute:NSLayoutAttributeWidth
multiplier:0.75f
constant:0.0f
]];
/**
* Constraints for the caption text view - horizontal
*/
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[figCaptionTextView]|"
options:0
metrics:metrics
views:views
]];
}
#end
Incase you were also wondering, I have included the implementation for UIView+Autolayout
** UIView+Autolayout.m **
//
// UIView+Autolayout.m
// Anna Christoffer
//
// Created by Matthew Finucane on 19/12/2014.
// Copyright (c) 2014 Anna Christoffer. All rights reserved.
//
#import "UIView+Autolayout.h"
#implementation UIView (Autolayout)
+(id)autoLayoutView {
UIView *view = [self new];
view.translatesAutoresizingMaskIntoConstraints = NO;
return view;
}
#end
You can try:
[captionImageView setClipsToBounds:YES];
try it right after you set aspect mode...
this might help: Crop UIImage to fit a frame image
EDIT
I just said about clipToBounds because even using autolayout from interface builder i had problems with my images in AspectFill mode, they would not respect the bounds of the imageview. But seems like your problem has more to do with layout constraints.
Try these constraints (notice the - sign next to the superview denoted as pipe |), it just adds the default aqua space to the constraints, it's simple but may work:
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[captionImageView]-|" options:0 metrics:metrics views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[captionTextView]|" options:0 metrics:metrics views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[captionImageView(imageHeight)][captionTextView]|" options:NSLayoutFormatAlignAllLeft metrics:metrics views:views]];

Displaying views with programmatic auto layout

EDIT
I am using programmatic auto layout and this issue seems to be eluding me,
in this class
#interface FooterButtonView : UIView {
...
}
I am trying to line up two views side by side
- (void)setUpViewWithTwoElements:(UIView*)element1 :(UIView*)element2{
element1.translatesAutoresizingMaskIntoConstraints = NO;
element2.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary* views = #{#"element1":element1, #"element2":element2};
NSDictionary* metrics = #{#"buttonHeight":#30.0};
NSString* horizontalFormatString = #"H:|-[element1]-[element2]-|";
NSString* verticalFormatString = #"V:[element1(buttonHeight)]-|";
[self addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:horizontalFormatString
options: NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom
metrics:nil
views:views]];
[self addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:verticalFormatString
options:nil
metrics:metrics
views:views]];
}
however neither elements is being displayed.
in init I am adding both subviews and then calling the above function. Both elements descend from UIButton.
Any suggestions are greatly appreciated. Thank you!
You should see something with the code you posted assuming that the init method that calls this code is itself being called (and FooterButtonView is being displayed with a non-zero size). One thing you're missing though is the relative horizontal sizes of the two views. With the code you have, there's no way for the system to know what size each of the elements should be, just that they should take up the whole width minus the standard spacings. If you want the two views to be the same size, then change to this,
NSString* horizontalFormatString = #"H:|-[element1]-[element2(==element1)]-|";

Autolayout Constraints on empty UIView do not work as expected

So I have setup a UIView that contains a UIScrollView (and child content view) that has sub views that are series of UILabels and UIViews that grow and shrink depending on the content contained in them, all using AutoLayout from the Storyboard. This works when I have something like Label - Label - Label - View w/o any issues, however if I put an empty UIView in-between two labels and insert sub views on the UIView, I'm not seeing the results I'm expecting. I have the following layout in a storyboard:
...where the teal and blue views are labels that grow to infinite height and the orange view (optionsPanel) is an empty UIVIew that I later inject sub views into. The rest of the stuff on the window is UILabels and UISegment controls. Between each row of views I have a Vertical Space constraint with a constant of 8. This all worked beautifully until I had to put in the empty UIView and programmatically inject sub views. The code I would expect to work would be something like (optionsPanel is the orange colored UIView)...
optionsPanel.translatesAutoresizingMaskIntoConstraints = YES;
NSArray *options = [product objectForKey:#"options"];
lastTop = 10;
for(int i=0;i<options.count; i++) {
NSDictionary *option = [options objectAtIndex:i];
NSArray *values = [option objectForKey:#"values"];
if([self hasNoneValue:values] && values.count == 2) {
NSDictionary *value = [self notNoneValue:values];
M13Checkbox *optionCheck = [[M13Checkbox alloc] initWithTitle:[option objectForKey:#"name"]];
optionCheck.frame = CGRectMake(0, lastTop, 280, 25);
[optionsPanel addSubview:optionCheck];
lastTop += 25;
} else {}
}
...where the orange UIView would magically grow and everything would just get pushed around accordingly, however this is what I'm seeing:
...the orange UIView does not grow at all, and the other two top UIView have gone somewhere off the screen. So my next guess was to turn off the Autoresizing Mask using...
optionsPanel.translatesAutoresizingMaskIntoConstraints = NO;
...but I'm getting a result where everything appears to be working but the orange UIView (optionsPanel) has no height for whatever reason and looks like:
This is getting closer to what I would expect, so I thought I would force the height of the orange UIView using code like...
frame = optionsPanel.frame;
frame.size.height = lastTop;
optionsPanel.frame = frame;
...but this appears to have no affect on anything.
Purely guessing, I found that this code almost works, if I arbitrary set the optionPanel's origin to something much larger than the space that is needed....
optionsPanel.translatesAutoresizingMaskIntoConstraints = YES;
NSArray *options = [product objectForKey:#"options"];
lastTop = 10;
for(int i=0;i<options.count; i++) {
NSDictionary *option = [options objectAtIndex:i];
NSArray *values = [option objectForKey:#"values"];
if([self hasNoneValue:values] && values.count == 2) {
NSDictionary *value = [self notNoneValue:values];
M13Checkbox *optionCheck = [[M13Checkbox alloc] initWithTitle:[option objectForKey:#"name"]];
optionCheck.frame = CGRectMake(0, lastTop, 280, 25);
[optionsPanel addSubview:optionCheck];
lastTop += 25;
} else {}
}
lastTop += 10;
frame = optionsPanel.frame;
frame.size.height = lastTop;
frame.origin.y += 300; //some arbitrarily‎ large number
optionsPanel.frame = frame;
..which gives this result:
...but apparently the AutoLayout has decided that the name label needs to take up the extra space. Its an ugly approach but if I could figure out how much space I need then I could just push everything down, if I had to. What's the secret to having a dynamic UIView between two dynamically sized labels and everything just work???
As #Timothy says, you need to manually add constraints to the subviews of the orange view if you want it to resize based on its contents—views don’t do this by default.
In general, if you’re using autolayout in a window, you should never be manually setting the frame of any view. Autolayout overrides any frames you set the every time it’s called, so even if you manage to manually get it working for a second it’ll fail the next time anything triggers a layout.
For views created in code, it's perfectly fine to set their frames as long as their translatesAutoresizingMaskIntoConstraints property is YES (the default, by the way).
However, for a view instantiated in storyboard or a nib, you can not set its translatesAutoresizingMaskIntoConstraints to YES.

Resources