i'm tying to programmatically create a scrollView that will show a description.
my hierarchy is probably going to be like this:
->root View
--->scroll View
----->content View
------->Label1
------->Label2
------->Label3
here is an image that shows the structure of the app:
this is the code so far, i get what i want except that now i need to include all this inside a scrollView (that will scroll only vertically).
i tried many different things but i still don't get how to do it.
UIView *contentView = [UIView autolayoutView];
contentView.backgroundColor = [UIColor greenColor];
[self.view addSubview:contentView];
NSDictionary *views2 = NSDictionaryOfVariableBindings(contentView);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[contentView]|" options:0 metrics:0 views:views2]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[contentView]" options:0 metrics:0 views:views2]];
UILabel *one = [UILabel autolayoutView];
UILabel *two = [UILabel autolayoutView];
UILabel *three = [UILabel autolayoutView];
for(UILabel *label in #[one,two,three]){
label.backgroundColor = [UIColor redColor];
label.text = #"fjeif\njeif\noesjf\nfjeif\njeif\noesjf\nfjeif\njeif\noesjf\nosfj";
label.numberOfLines = 0;
[contentView addSubview:label];
}
NSDictionary *views3 = NSDictionaryOfVariableBindings(one,two,three);
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-[one]-|" options:0 metrics:0 views:views3]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[one(two)]-[two(three)]-[three]-|" options:NSLayoutFormatAlignAllLeft | NSLayoutFormatAlignAllCenterX metrics:0 views:views3]];
thanks, sorry if i didn't describe my problem well enough, i'm in a hurry. looking at the image should be enough to understand though.
I had the same problem though I set all constraints from XIB. You can do like this.
Add leading edge, trailing edge, top space and bottom space constraints to labels with respect to container view. Then you can add these same constraints to container view with respect to scrollView. Do the same exercise for scrollView.
You just need to add one more constraint (Horizontally center in container view constraint)to container view.
Hope this will help you.
Related
I am trying to create a view setup vertically where i have one UIView (fixed width, height) at the top and two UILabels (fixed width, dynamic height) at the bottom. Padding all around the view (aView) is 5. Padding all around of _mylabel is 5. Padding on left and right of _yourLable is 5. _yourLable will grow as based on text, but when text content is too large, it will just stop to grow for maintain padding from superview of 5.
This is what i have tried:
- (void)viewDidLoad {
[super viewDidLoad];
UIView *superview = self.view;
UIView *aView = [UIView new];
[aView setBackgroundColor:[UIColor blackColor]];
[aView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:aView];
_mylabel = [[UILabel alloc]init];
[_mylabel setNumberOfLines:0];
[_mylabel setBackgroundColor:[UIColor redColor]];
[_mylabel setTranslatesAutoresizingMaskIntoConstraints:NO];
_mylabel.text = #"i am trying to create a view setup vertically where i have one UIView(fix width, height) at top and other two UILables(fix width, dynamic height) at bottom respectively. Padding on allaround of view/lables is 5. this is what i have tried:";
[self.view addSubview:_mylabel];
_yourLable = [[UILabel alloc]init];
[_yourLable setNumberOfLines:0];
[_yourLable setBackgroundColor:[UIColor redColor]];
[_yourLable setTranslatesAutoresizingMaskIntoConstraints:NO];
_yourLable.text = #"i am trying to create a view setup vertically where i have one UIView(fix width, height) at top and other two UILables(fix width, dynamic height) at bottom respectively. Padding on allaround of view/lables is 5. this is what i have tried:";
[self.view addSubview:_yourLable];
NSDictionary * views = NSDictionaryOfVariableBindings(aView,_mylabel, _yourLable, superview);
NSArray * heightConstraintforLabel = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-5-[aView(==200)]-5-[_mylabel]-5-[_yourLable]-5-|" options:0 metrics:nil views:views];
NSArray * widthConstraintforView = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-5-[aView]-5-|" options:0 metrics:nil views:views];
NSArray * widthConstraintformylabel = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-5-[_mylabel]-5-|" options:0 metrics:nil views:views];
NSArray * widthConstraintforyourLable = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-5-[_yourLable]-5-|" options:0 metrics:nil views:views];
[superview addConstraints:heightConstraintforLabel];
[superview addConstraints:widthConstraintforView];
[superview addConstraints:widthConstraintformylabel];
[superview addConstraints:widthConstraintforyourLable];
}
and
-(void)viewWillLayoutSubviews{
[super viewWillLayoutSubviews];
// Your layout logic here
CGFloat availableLabelWidth = _mylabel.frame.size.width;
_mylabel.preferredMaxLayoutWidth = availableLabelWidth;
availableLabelWidth = _yourLable.frame.size.width;
_yourLable.preferredMaxLayoutWidth = availableLabelWidth;
}
This is what i am getting, without warnings:
I want both labels to resize based on exact text height.
I want last red label to grow as per text written in it, but never go beyond bottom space of 5. That is it should grow but maintain bottom padding of 5.
I have tried various combination with vertical content compression for labels..., but not got exact solution.
Help :)
Just in case you have not set the priorities, use
[_mylabel setContentCompressionResistancePriority:751 forAxis:UILayoutConstraintAxisVertical];
[_mylabel setContentHuggingPriority:252 forAxis:UILayoutConstraintAxisVertical];
You don't need to set any priorities for _yourLable.
And you don't need to set the preferredMaxLayoutWidth for any of the labels in viewWillLayoutSubviews, hence you don't need to override viewWillLayoutSubviews. You can comment the whole method.
Verified on iOS 7 and iOS 9 devices.
Simulator screenshot looks like this,
I have been searching for the whole day, but could not make it work.
I have a UIView with two labels, on above the other, on the left side, and one button on the right side. I added the constraints to let autolayout resize views accordingly. Everything was working perfectly when I had set one constraint for the height of the UIView (and no one constraint for the height of the two UILabel), but as the content of the lower UILabel will vary, I removed that constraint and set two constraints for the UILabel, one fixed for the upper UILabel and one with relation "greater than or equal" for the lower UILabel, and other one to fix the distance between the lower UILabel and the UIView.
It looks like auto layout is not capable of calculate the intrinsicContentSize of the lower UILabel, because it never increases its height above 10px, despite the content of the lower UILabel.
UIView *miView = [[UIView alloc] init];
UILabel *miLabel = [[UILabel alloc] init];
[miLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[miDetailsLabel setNumberOfLines:1];
[miDetailsLabel setText:#"Just one line."];
[miView addSubview:miLabel];
UILabel *miDetailsLabel = [[UILabel alloc] init];
[miDetailsLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[miDetailsLabel setNumberOfLines:0];
[miDetailsLabel setText:#"Enough text to show 3 lines on an IP4, except first of three UILabels on my test code, with no content"];
[miView addSubview:miDetailsLabel];
UIButton *miButton = [[UIButton alloc] init];
[miButton setTranslatesAutoresizingMaskIntoConstraints:NO];
[miView addSubview:miButton];
NSArray *constraint_inner_h = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(0)-[label]-(10)-[button(52)]-(0)-|" options:0 metrics:nil views:#{#"label":miLabel, #"button":miButton}];
NSArray *constraint_inner2_h = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(0)-[details]-(10)-[button]-(0)-|" options:0 metrics:nil views:#{#"details":miDetailsLabel, #"button":miButton}];
NSArray *constraint_label_v = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(0)-[label(18)]-(2)-[details(>=10)]-(0)-|" options:0 metrics:nil views:#{#"label":miLabel, #"details":miDetailsLabel}];
NSArray *constraint_button_v = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[button(22)]" options:0 metrics:nil views:#{#"button":miButton}];
[miView addConstraint:[NSLayoutConstraint constraintWithItem:miButton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:miView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[miView addConstraints:constraint_inner_h];
[miView addConstraints:constraint_inner2_h];
[miView addConstraints:constraint_label_v];
[miView addConstraints:constraint_button_v];
I ve put a reduced version of the code.
any ideas of what I am missing?
Thanks
UPDATE : Thanks to Matt advice, I've tried this solution to set the proper value to preferredMawLayoutWidth:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSArray *allKeys = [dictOpcionesTickets allKeys];
for (NSString *key in allKeys) {
NSArray *tmpArray = [dictOpcionesTickets objectForKey:key];
if (![[tmpArray objectAtIndex:0] isEqual:#""]) {
UILabel *tmpLabel = [tmpArray objectAtIndex:3];
tmpLabel.preferredMaxLayoutWidth = tmpLabel.frame.size.width;
NSLog(#"width: %f",tmpLabel.frame.size.width);
}
}
[self.view layoutIfNeeded];
}
To explain the code, as I said, I'm doing it dynamically, so I've created a dictionary with an array with the references to my UILabel (among other interesting information). When I run the code, I get the next log (with the code parsing 3 UILabel, first label with no content):
2014-12-28 20:40:42.898 myapp[5419:60b] width: 0.000000
2014-12-28 20:40:42.912 myapp[5419:60b] width: 0.000000
2014-12-28 20:40:43.078 myapp[5419:60b] width: 229.000000
2014-12-28 20:40:43.080 myapp[5419:60b] width: 229.000000
2014-12-28 20:40:43.326 myapp[5419:60b] width: 229.000000
2014-12-28 20:40:43.327 myapp[5419:60b] width: 229.000000
But I'm still getting the same result...the UIView is still showing a height equals to the minimum height set by the constraints, not showing the content of the UILabel.
UPDATE 2 Still fooling around.
I've tested my initial code but simplified on a fresh project in xcode 6, running on an actual iPhone 4 with iOS7, and it worked perfectly without setting preferredMaxLayoutWidth or subclassing UILabel, and even without calling layoutIfNeeded on parent view. But when it comes to the real project (I think it was originally builded in xcode 4 or 5) it does not work:
- (void)addLabelsDinamically {
self.labelFixed.text = #"Below this IB label goes others added dinamically...";
UIButton *miBoton = [[UIButton alloc] init];
miBoton.translatesAutoresizingMaskIntoConstraints = NO;
[miBoton setTitle:#"Hola" forState:UIControlStateNormal];
[self.viewLabels addSubview:miBoton];
[self.viewLabels addConstraint:[NSLayoutConstraint constraintWithItem:miBoton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.viewLabels attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[boton(22)]" options:0 metrics:nil views:#{#"boton":miBoton}]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[boton(40)]" options:0 metrics:nil views:#{#"boton":miBoton}]];
UILabel *miLabel = [[UILabel alloc] init];
miLabel.translatesAutoresizingMaskIntoConstraints = NO;
miLabel.backgroundColor = [UIColor redColor];
miLabel.numberOfLines = 0;
miLabel.text = #"ñkhnaermgñlkafmbñlkadnlñejtnhalrmvlkamnñnañorenoetñnngñsdbmnñgwn";
[self.viewLabels addSubview:miLabel];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(8)-[label]-(10)-[boton]-(8)-|" options:0 metrics:nil views:#{#"label":miLabel,#"boton":miBoton}]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[previous]-(8)-[label]" options:0 metrics:nil views:#{#"label":miLabel,#"previous":self.labelFixed}]];
UIView *pre = miLabel;
miLabel = [[UILabel alloc] init];
miLabel.translatesAutoresizingMaskIntoConstraints = NO;
miLabel.backgroundColor = [UIColor greenColor];
miLabel.numberOfLines = 0;
miLabel.text = #"ñkhnaermgñlkafmbñlkadnlñejtnhalrmvlkamnñnañorenoetñnngñsdbmnñgwn";
[self.viewLabels addSubview:miLabel];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(8)-[label]-(10)-[boton]-(8)-|" options:0 metrics:nil views:#{#"label":miLabel,#"boton":miBoton}]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[previous]-(8)-[label]" options:0 metrics:nil views:#{#"label":miLabel,#"previous":pre}]];
}
Ah, on the same screen I have one UILabel added on IB, with numberOfLines set to '0', vertical constraint "greater or equal than" and horizontal constraint set in a similar way (but both constraints set on IB)...and it works perfectly...this is driving me nuts!! any help???
Well, finally I've found the error, as I stated in the "UPDATE 2", it was very strange that it was working on an UILabel added via IB, but not with those added dynamically.
So, I looked again to my screen on IB, and found that I have an height constraint with "equal" relation, when I changed it to "greater or equal than" it all started to work!!!! Why I did not notice that the constraint was not "greater or equal than"? Because the view with the constraint was getting larger and larger as I was adding views with labels inside. Is that a bug?
Well, to summarize if someone find this useful. "How to add UILabels multi-line which adapts to its content?"
In iOS 6 versions it was a bug with "preferredMaxLayoutWidth", not getting the width once it was "layouted", and as a result of that programmers must set it via code (the best solution is doing it dynamically, an example of that I put it on my above question).
Fortunately on iOS7 and later, this has been solved, and you can rely only with constraints.
Adding via IB
Add the UILabel, and set "number of lines" to '0' (this will make the UILabel use as many lines as it needs).
Add the constraints, you need at least 3 constraints (pin to top or bottom, and both sides), the fourth will be height with a relation "greater or equal than".
Be sure than the view container has enough room, or also has a height constraint with a relation "greater or equal than".
Adding via code (just paste the code posted above):
UILabel *miLabel = [[UILabel alloc] init]; // Initialitze the UILabel
miLabel.translatesAutoresizingMaskIntoConstraints = NO; // Mandatory to disable old way of positioning
miLabel.backgroundColor = [UIColor redColor];
miLabel.numberOfLines = 0; // Mandatory to allow multiline behaviour
miLabel.text = #"ñkhnaergñsdbmnñgwn"; // big test to test
[self.viewLabels addSubview:miLabel]; // Add subview to the parent view
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(10)-[label]-(10)-|" options:0 metrics:nil views:#{#"label":miLabel}]]; // horizontal constraint
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(10)-[label]" options:0 metrics:nil views:#{#"label":miLabel}]];
UIView *pre = miLabel;
// Adding a second label to test the separation
miLabel = [[UILabel alloc] init];
miLabel.translatesAutoresizingMaskIntoConstraints = NO;
miLabel.backgroundColor = [UIColor greenColor];
miLabel.numberOfLines = 0;
miLabel.text = #"ñkhnaermgñlkafmbnoetñnngñsdbmnñgwn";
[self.viewLabels addSubview:miLabel];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(10)-[label]-(10)-|" options:0 metrics:nil views:#{#"label":miLabel,#"boton":miBoton}]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[previous]-(10)-[label]" options:0 metrics:nil views:#{#"label":miLabel,#"previous":pre}]];
Thanks to those who has tried to help me!! This site is amazing :)
This answer will work in a programmatic situation when you are added the text information dynamically and if this above is on a UICollectionViewCell or UITableViewCell.
In these situations you will need to update the layout and constraints on the contentView, then set the preferedWidth:
-(void) layoutSubviews
{
[super layoutSubviews];
[self.contentView setNeedsLayout];
[self.contentView layoutIfNeeded];
//locationName.preferredMaxLayoutWidth = locationName.frame.size.width;
//locationDetails.preferredMaxLayoutWidth = locationDetails.frame.size.width;
[super layoutSubviews];
}
This is similar to other answers where u are getting the new frame information and using that for the preferredMaxLayoutWidth then updating the view.
You will need constraints added to those views for the vertical as a start point and the UILabel need their numberOfLines = 0
On second thoughts setting he preferredMaxLayoutWidth in the layoutSubViews is poor solution if your width changes, as well as the height. Best to set this else where like the init for that cell. In some ways it might not be needed if you are constraining the width. This in my situation lead to the preferredMaxLayoutWidth changing each time that cell was set.
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];
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?
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.