Subview width not fitting superview with autolayout - ios

I am adding a subview to a UICollectionViewCell. I am programmatically adding constraints to make it fill the cell, however the width does not fill the cell.
When I view it in the view debugger, it says the position is ambiguous. How can this be since I am specifying all 4 sides are pinned to the superview?
This is what the views look like in the debugger. The inner, white view should fit the parents width (blue border):
Inspecting the constraints on the parent view shows this with the "position ambiguous" warning:
The code I am using is as follows:
[self.contentView addSubview:calloutView];
calloutView.translatesAutoresizingMaskIntoConstraints = NO;
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[calloutView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(calloutView) ]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[calloutView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(calloutView) ]];

The problem seems to be related to the use of contentView, you must use the cell itself to achieve what you want, this is the code and works, check the picture below
#import "CollectionViewCell.h"
#interface CollectionViewCell()
#property UIView * testView;
#end
#implementation CollectionViewCell
#synthesize testView;
-(void)awakeFromNib
{
[super awakeFromNib];
testView = [[UIView alloc]initWithFrame:CGRectZero];
testView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:testView];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-2-[testView]-2-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(testView) ]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-2-[testView]-2-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(testView) ]];
self.testView.layer.borderWidth = 2;
self.testView.layer.borderColor = [UIColor blueColor].CGColor;
}
#end
I hope this helps you, best regards

Related

Constrain width of UIStackView arranged views inside UIScrollView

I have embedded a UIStackView inside a UIScrollView. When I add a view as an arranged subview to the UIStackView, its width expands beyond the frame and causes a horizontal scrollbar to appear, even when I add horizontal width layout constraints. I do not want the UIScrollView to expand horizontally.
The code for programatically creating the UIStackView inside the UIScrollView in viewDidLoad:
_scrollView = [UIScrollView new];
_scrollView.delegate = self;
_scrollView.translatesAutoresizingMaskIntoConstraints = NO;
_scrollView.pagingEnabled = YES;
[self.view addSubview:_scrollView];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:#{#"scrollView": _scrollView}]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:#{#"scrollView": _scrollView}]];
// configure stackView
_stackView = [UIStackView new];
_stackView.translatesAutoresizingMaskIntoConstraints = NO;
_stackView.axis = UILayoutConstraintAxisVertical;
[_scrollView addSubview:_stackView];
[_scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[stackView]|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:#{#"stackView": _stackView}]];
[_scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[stackView]|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:#{#"stackView": _stackView}]];
Then I add my custom view, and an AutoLayout constraint to limit the subview's width to the stackView's:
CustomView *customView = [CustomView loadFromXib]; // helper to load from xib
[_stackView addArrangedSubview:customView];
[_stackView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[customView]|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:#{#"customView": customView}]];
However, the resulting CustomView is about double the width of the frame. None of the IB-set constraints in CustomView have fixed widths, so I would expect the content to resize to fit the frame. What am I doing wrong?

Adding a background imageview programmatically with autolayout

I need to add a background image view for my views for a project I've done using storyboards + autolayout. I want to add this image programmatically using code. so basically it should be from top layoutguide to bottom layoutguide, without going under them. I've tried few ways which failed horribly.
one way I first adjust the VC'c view before adding like this
id topGuide = self.topLayoutGuide;
UIView *superView = self.view;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings (superView, topGuide);
[self.view addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:[topGuide]-20-[superView]"
options:0
metrics:nil
views:viewsDictionary]
];
[self.view layoutSubviews];
but for some reason my imageview still goes under statusbar.
this is how I add the bg imageview
self.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Default"]];
self.backgroundView.contentMode = UIViewContentModeTop;
[self.view insertSubview:self.backgroundView atIndex:0];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[backgroundImageView]|" options:0 metrics:nil views:#{#"backgroundImageView":self.backgroundView}]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[backgroundImageView]|" options:0 metrics:nil views:#{#"backgroundImageView":self.backgroundView}]];
Adding the constraint related to the topLayoutGuide to self.view is useless. The view controller layout its root view (self.view) independently from AutoLayout, and will override the constraints effects (don't quote me on this, this is an observation more than a real understanding of the layout system).
Instead, add the first constraint (#"V:[topGuide]-20-[superView]") to self.backgroundView:
self.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Default"]];
self.backgroundView.contentMode = UIViewContentModeTop;
[self.view insertSubview:self.backgroundView atIndex:0];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[topGuide]-(20)-[backgroundImageView]|" options:0 metrics:nil views:#{#"backgroundImageView":self.backgroundView, #"topGuide": self.topLayoutGuide}]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[backgroundImageView]|" options:0 metrics:nil views:#{#"backgroundImageView":self.backgroundView}]];
[self.view layoutSubviews];

How do I make my superview resize to match the size of its subviews with Autolayout?

I've got a UIView ("superview") that has a couple of UILabels as subviews, set up in Interface Builder. I've got Auto-Layout turned on to properly space all the labels in a list, one after another. This works well.
What I'm trying to do now is make it so that my Superview resizes vertically to match the height of all my labels and I can't quite figure out how to do this.
How does this work?
Here's an example using visual layouts... so ignore the translatesAutoresizingMaskIntoConstraints business.
This relies on the fact that the container view does NOT have any height constraints and it seems to rely on the spacing between views. The system will resize the view to match the requirements of the subviews.
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor redColor];
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
_labelOne = [[UILabel alloc] init];
_labelOne.text = #"Banana";
_labelOne.backgroundColor = [UIColor blueColor];
[_labelOne setTranslatesAutoresizingMaskIntoConstraints:NO];
_labelTwo = [[UILabel alloc] init];
_labelTwo.text = #"Dinosaur";
_labelTwo.backgroundColor = [UIColor yellowColor];
[_labelTwo setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:_labelOne];
[self addSubview:_labelTwo];
NSDictionary *views = NSDictionaryOfVariableBindings(_labelOne, _labelTwo);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_labelOne]|" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_labelTwo]|" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-10-[_labelOne(30)]-15-[_labelTwo(30)]-10-|" options:0 metrics:nil views:views]];
}
return self;
}
I know the basic idea is to have the intrinsic content size of your subviews drive the height of superview by making sure the content compression resistance and content hugging constraints in the vertical dimension for each subview are not being overridden by higher-priority constraints you have added.
Try use the UIView method sizeToFit?

UIScrollView using pure auto layout not expanding right

I've read the official documentation from Apple on auto layout and UIScrollView:
RN-iOSSDK-6_0
I'm trying the "Pure Auto Layout approach".
I'm adding a UILabel and a UITextField. I want both to expand to the width of the interface:
RootViewController.h
#interface rootViewController : UIViewController
#property (nonatomic, strong) UIScrollView *scrollView;
#property (nonatomic, strong) UIView *contentView;
#property (nonatomic, strong) UILabel *bigLetters;
#property (nonatomic, strong) UITextField *aTextField;
#end
viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
self.view.backgroundColor = [UIColor lightGrayColor];
_scrollView = [[UIScrollView alloc] init];
[_scrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
_contentView = [[UIView alloc] init];
[_contentView setTranslatesAutoresizingMaskIntoConstraints:NO];
_bigLetters = [[UILabel alloc] init];
[_bigLetters setTranslatesAutoresizingMaskIntoConstraints:NO];
_bigLetters.font = [UIFont systemFontOfSize:30];
_bigLetters.textColor = [UIColor blackColor];
_bigLetters.backgroundColor = [UIColor clearColor];
_bigLetters.text = #"Why so small?";
_bigLetters.textAlignment = NSTextAlignmentCenter;
_aTextField = [[UITextField alloc] init];
_aTextField.backgroundColor = [UIColor whiteColor];
_aTextField.placeholder = #"A Text Box";
[_aTextField setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:_scrollView];
[self.scrollView addSubview:_contentView];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_scrollView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_scrollView)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_scrollView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_scrollView)]];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_contentView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_contentView)]];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_contentView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_contentView)]];
[self.contentView addSubview:_bigLetters];
[self.contentView addSubview:_aTextField];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[_bigLetters]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_bigLetters)]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[_aTextField]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_aTextField)]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[_bigLetters]-[_aTextField]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_bigLetters, _aTextField)]];
// Do any additional setup after loading the view.
}
I get both elements squished against the top left corner like this:
If I switch _scrollView to a UIView, things look fine. What am I doing wrong?
Thanks!
UIScrollViews are a little bit complicated.
Firs of all:
[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
You should not be doing that. Your root view does not need to work with autolayout. Your subviews need it.
Also, the contentSize of the scrollview is defined by what you put inside of them. If you want to have a scrollView screen-sized fix one of it's subviews to screen width (or eight).
What you are seeing there, is your scrollview wrapping up it's content, and the width is probably the label intrinsic content size.
That's why it works with a View.
Hope it helps!!

UIScrollView with iOS Auto Layout Constraints: Wrong size for subviews

I'm trying to generate a view in code. Here's the hierachy of my view object
UIScrollView
UIView
UIButton
The ScrollView should be the same size as the window.
The button should be as big as possible.
I'm using iOS auto layout, so the constraint strings for all of my objects look like this
H:|[object]|
V:|[object]|
I've also set translatesAutoresizingMaskIntoConstraints to NO for each object.
The problem is that the button only gets the default button-size. Its parent view object (UIView) only gets the size its subviews need.
red: UIScrollView / yellow: UIView
How can I force those views to be as big as the scrollView?
When I use a UIView instead of th UIScrollView everything works great...
Here's some code:
- (void) viewDidLoad {
[super viewDidLoad];
// SCROLL VIEW
UIScrollView* scrollView = [UIScrollView new];
scrollView.backgroundColor=[UIColor redColor];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
//CONTAINER VIEW
UIView *containerView = [UIView new];
containerView.translatesAutoresizingMaskIntoConstraints = NO;
containerView.backgroundColor = [UIColor yellowColor];
[scrollView addSubview:containerView];
// CONSTRAINTS SCROLL VIEW - CONTAINER VIEW
[scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[containerView]|"
options:0 metrics:nil
views:#{#"containerView":containerView}]];
[scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[containerView]|"
options:0 metrics:nil
views:#{#"containerView":containerView}]];
// BUTTON
UIButton* button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.translatesAutoresizingMaskIntoConstraints = NO;
[button setTitle:#"I'm way to small" forState:UIControlStateNormal];
[containerView addSubview:button];
// CONSTRAINTS CONTAINER VIEW - BUTTON
[containerView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[button]|"
options:0 metrics:nil
views:#{#"button":button}]];
[containerView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[button]|"
options:0 metrics:nil
views:#{#"button":button}]];
self.view = scrollView;
}
UPDATE:
I really don't know, why this is happening. If you set up the view in IB, connect the outlets and instanciate the view in code, the scrollview behaves like a normal view (which bounces vertically). Its contentSize is not calculated correctly. More here. But how to do it correctly?
A couple of observations:
Constraints for subviews in scroll views don't work like constraints in other views. They're used to set the contentSize of the scroll view. (See TN2154.) That way, you throw a bunch of stuff on a scroll view, set the constraints for the stuff inside it, and the contentSize is calculated for you. It's very cool feature, but it's antithetical to what you're trying to do here.
Worse, buttons will, unless you set an explicit constraint for their width and height of a button, will resize according to their content.
The net effect of these two observations is that your existing constraints say "(a) set my container to be the size of my button; (b) let my button resize itself dynamically to the size of the text; and (c) set my scrollview's contentSize according to the size of my container (which is the size of the button)."
I'm unclear as to what the business problem is. But here are some constraints that achieve what I think your technical question was:
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *view = self.view;
UIScrollView *scrollView = [[UIScrollView alloc] init];
scrollView.backgroundColor = [UIColor redColor]; // just so I can see it
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:scrollView];
UIView *containerView = [[UIView alloc] init];
containerView.backgroundColor = [UIColor yellowColor]; // just so I can see it
containerView.translatesAutoresizingMaskIntoConstraints = NO;
[scrollView addSubview:containerView];
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.translatesAutoresizingMaskIntoConstraints = NO;
[button setTitle:#"I'm the right size" forState:UIControlStateNormal];
[containerView addSubview:button];
NSDictionary *views = NSDictionaryOfVariableBindings(scrollView, button, view, containerView);
// set the scrollview to be the size of the root view
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|"
options:0
metrics:nil
views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|"
options:0
metrics:nil
views:views]];
// set the container to the size of the main view, and simultaneously
// set the scrollview's contentSize to match the size of the container
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[containerView(==view)]|"
options:0
metrics:nil
views:views]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[containerView(==view)]|"
options:0
metrics:nil
views:views]];
// set the button size to be the size of the container view
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[button(==containerView)]"
options:0
metrics:nil
views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[button(==containerView)]"
options:0
metrics:nil
views:views]];
}
Frankly, I don't understand the business intent of your UI, as this feels like a contortion of auto layout to achieve a very simply UI. I don't know why you have a scroll view if you have "screen sized" content in it (unless you were paging through buttons). I don't know why you'd have a content view with a single item in it. I don't understand why you're using a full-screen button (I'd just put a tap gesture on the root view at that point and call it a day).
I'll assume you have good reasons for all of this, but it might make sense to back up, ask what is your desired user experience is, and then approach the problem fresh to see if there's a more efficient way to achieve the desired effect.

Resources