I have a UIScrollView that is to show UIImageViews. The ImageViews are programmatically inserted at runtime based on how many images are saved by the user previously. I get the error below:
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one
you don't want. Try this: (1) look at each constraint and try to
figure out which you don't expect; (2) find the code that added the
unwanted constraint or constraints and fix it. (Note: If you're seeing
NSAutoresizingMaskLayoutConstraints that you don't understand, refer
to the documentation for the UIView property
translatesAutoresizingMaskIntoConstraints)
"<NSLayoutConstraint:0x17029cd90 H:[UIView:0x15cd31e70]-(0)-| (Names: '|':UIScrollView:0x15cd326b0 )>",
"<NSLayoutConstraint:0x17029d060 UIScrollView:0x15cd326b0.trailingMargin == UIView:0x15cd31e70.trailing>"
I do the basic Autolayout thing where the scrollview is pinned to all four sides at 0 points. I then add a contentView as a subview (plain UIView) of the UIScrollView which is also pinned to all four sides at 0 points.
EDIT Storyboard constraints image
I give the contentView a width in code like so:
CGSize pagesScrollViewSize = self.scrollView.frame.size;
NSDictionary *views = #{ #"contentView" : self.contentView};
NSDictionary *metrics = #{ #"width" : #(pagesScrollViewSize.width * self.mutableArray.count) };
NSArray *constraints;
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[contentView(width)]-|" options:0 metrics:metrics views:views];
[self.scrollView addConstraints:constraints];
[self loadVisiblePages];
The UIImageViews are added like so where UIImageViews are added based on the number of pages set when the segue to the ViewController occurs.
-(void)loadVisiblePages{
CGRect frame = self.scrollView.bounds;
frame.origin.x = frame.size.width * self.page;
frame.origin.y = 0.0f;
ImageForArchiving *newImageObject = (ImageForArchiving*)self.mutableArray[page];
UIImage *imageForNewPageView = newImageObject.image;
UIImageView *newPageView = [[UIImageView alloc] initWithFrame:frame];
[newPageView setTranslatesAutoresizingMaskIntoConstraints:YES];
newPageView.contentMode = UIViewContentModeScaleAspectFit;
newPageView.image = imageForNewPageView;
[self.scrollView addSubview:newPageView];
[self.pageViews replaceObjectAtIndex:page withObject:newPageView];
}
}
Additionally, when I scroll the UIScrollView the images displayed change size erratically on rotation. I think that this is just a consequence of the above warning and the fact that I haven't layed out the UIImageViews yet. What does the above warning mean in this context and how do I fix it?
It seems you have pinned trailingMargin of scrollView to contentView.trailing.
Change scrollView.Trailing Margin to scrollView.Trailing for this constraint
You can do this in the activity inspector in the storyboard after selecting your constraint.
Alternatively, clear all constraints on your contentView. Then while adding pinning constraints again uncheck Constrain to margins and set all constants 0.
AND
Change this line in your code
NSArray *constraints;
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[contentView(width)]-|" options:0 metrics:metrics views:views];
[self.scrollView addConstraints:constraints];
with this:
NSArray *constraints;
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[contentView(width)]|" options:0 metrics:metrics views:views];
[self.scrollView addConstraints:constraints];
Using #"H:|-[contentView(width)]-|" in visual format means pinning your contentView to the superView's margins and adds a space of 8 pts between the superView and subView. In your storyboard constraints you had set up constraints with the Trailing and Leading edges of the UIScrollView, while in the programmatically added constraint you had used Trailing Margin and Leading Margin (kind of asking the contentView to maintain an 8 pt. padding). Hence, the conflict.
Check Visual Format syntax here.
Related
This question already has answers here:
UITableView within UIScrollView using autolayout
(5 answers)
Closed 7 years ago.
I have this views hierarchy in a xib file:
UIView
UIScrollView
UIView
UIView
UITableView
UIButton
Let's call contentView the UIView that is the direct child of the UIScrollView. I've set its top, bottom, leading and trailing constraints to pin the scroll view. Then, since I'm populating the table view at runtime and I don't know its height beforehand, I set the scroll view's contentSize in code:
[self.scrollView setContentSize:CGSizeMake(self.contentView.frame.size.width, self.tableView.frame.size.height)];
But I don't make this work... what could I be missing?
AppsDev, check this video out it helped me a lot doing UIScrollView via storyboard
https://www.youtube.com/watch?v=UnQsFlMGDsI
Also never set the scrollView's contentSize as it has to be determined by scrollView on its own and that's why we have AutoLayout.
I believe we should have one tag for uiscrollview-autolayout
Don't set the content size manually. Instead,
Constrain your contentView's four edges to the edges of the scrollView.
Constrain your contentView's width to be equal to the scrollView's width. (This will prevent the content from being wider than the scrollView.)
Constrain the contentView's top and sides to the corresponding edges of the child view.
Constrain the contentView's sides and bottom to the sides and bottom of the tableView.
Now here's where it gets tricky: constrain the bottom of the child view to be equal to the top of the table view. However, unless you explicitly set a height constraint on the child view, you'll get a layout error that the height of the scrollView's contents will be ambiguous. To get around this, you can set the child's placeholder height to make Interface Builder happy, but then you'll also have to set its height somewhere at runtime.
Now you should be set. The scrollView can now calculate the full height and width of its contents by examining the constraint hierarchy, and you don't have to set its content height manually.
I finally managed to make this work by following the #Sana answer and also this post to be able to scroll the table view content.
Thanks u all for replying.
Just for example:
UIView *containerView = [[UIView alloc] init];
UIScrollView *scrollView = [[UIScrollView alloc] init];
[containerView addSubview:scrollView];
containerView.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(containerView, scrollView);
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|"
options:kNilOptions
metrics:nil
views:viewsDictionary]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|"
options:kNilOptions
metrics:nil
views:viewsDictionary]];
Use this code My code help you.
-(void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self.scrollView setContentSize:CGSizeMake(self.contentView.frame.size.width, self.tableView.frame.size.height)];
}
I have a scrollview and a separate UIView where I placed a series of textFields and labels with constraints which fully occupies the top and bottom. I'm trying to adjust the UIView's height based on its subview constraints but it won't. What is happening is that the view keeps its height and force other textfields to collapse or shrink thus breaking the constraints.
Details
Each subview priority values :
compression = 750
hugging = 250
UIView priority values:
compression = 249
hugging = 749 Set to be lower than the rest.
Most of the textfields has aspect ratio constraint. This causes the field to adjust.
Each subview has vertical/top/bottom spacing between each other. The top and bottom elements has top and bottom constraints to the view as well.
What's on my code:
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
/* I had to adjust the UIView's width to fill the entire self.view.*/
if(![contentView isDescendantOfView:detailsScrollView]){
CGRect r = contentView.frame;
r.size.width = self.view.frame.size.width;
contentView.frame = r;
[detailsScrollView addSubview:contentView];
}
}
Screenshots
The view
This is what currently happens. In this instance it forces the email field to shrink. If I place a height value on it, it does not shrink but the layout engine finds another element to break
Edit:
Solved
Maybe I just needed some break to freshen up a bit. I did tried using constraints before but got no luck. However thanks to the suggestion I went back setting the constraints instead of setting the frame on this one and got it finally working.
Solution:
-(void)viewDidLoad{
[detailsScrollView addSubview:contentView];
[contentView setTranslatesAutoresizingMaskIntoConstraints:NO];
[detailsScrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(contentView,detailsScrollView);
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[contentView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil
views:viewsDictionary];
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[contentView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:viewsDictionary];
NSArray *widthConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[contentView(==detailsScrollView)]-0-|" options:0 metrics:nil views:viewsDictionary];
}
When you use interface builder to deal with the UIScrollView and its child UIView. usually a top, bottom, left and equal width constraints are set between the UIScrollView and its child which is the contentView in your case.
Without those constraints the other option is to set the content size of the UIScrollView. which was the way of using the UIScrollView before introducing constraints.
So, 1. you should add those constraints programmatically.
By using the constraints, the views frame is no longer needed to resize the views.
So, 2. remove frame setting for your content view.
I am not so happy with the way you set the frame in the viewDidLayoutMethod. if I am going to do that here I would take the frame setting out of the if statement.
The code would be as follow with no if statement:
[detailsScrollView addSubview:contentView];
// then set the constraints here after adding the subview.
Put this code anywhere but not inside your viewDidLayoutSubviews method. it will be a bigger problem than setting the frame in there inside if statement.
Note: Originally, if you are going to set frame in the viewDidLayoutSubviews
method. you should do it for all cases. for example for the if case
and the else case. because, next time this method is going to be
called the views will respond to the constraint. and lose its frame.
Another observation: if you want the view to response to its subviews constraint why you need to set the frame for it? right?
After adding the constraint you may need to call the method constraintNeedsUpdate or another related method.
I'm fairly new to iOS programming and probably don't understand the view hierarchy as well as I should and thus am failing to successfully get two labels within a custom table cell class I have created to autoresize properly. Namely the "translatesAutoresizingMaskIntoConstraints" property has me a little confused.
I am not using storyboards for this part of the code: I have a TableViewController where I create my own tableView in viewDidLoad. In cellForRowAtIndexPath I init my own TableViewCell implementation.
The problem I'm having is that when I set "setTranslatesAutoresizingMaskIntoConstraints" to NO for the table view and the UILabels I create then add my constraints, I get the following error:
"Terminating app due to uncaught exception `'NSInternalInconsistencyException',` reason: 'Auto Layout still required after executing `-layoutSubviews`. UITableView's implementation of `-layoutSubviews` needs to call super.'"
If I comment out the setTranslatesAutoresizingMaskIntoConstraints lines, my app runs however I get the following warning about the constraints:
"Unable to simultaneously satisfy constraints. Probably at least one
of the constraints in the following list is one you don't want. Try
this: (1) look at each constraint and try to figure out which you
don't expect; (2) find the code that added the unwanted constraint or
constraints and fix it. (Note: If you're seeing
NSAutoresizingMaskLayoutConstraints that you don't understand, refer
to the documentation for the UIView property
translatesAutoresizingMaskIntoConstraints)"
Essentially what I want to do is to enter code here have the two labels flush against each other and for them to resize based on orientation/device (I will be setting a background colour on them so want them to look 'continuous')
Can anyone help me out and explain what I am missing? Thanks in advance.
My code for adding the labels is:
self.nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200.0f, 30.0f)];
self.nameLabel.textColor = [UIColor redColor];
self.nameLabel.font = [UIFont fontWithName:#"Helvetica Neue" size:12.0f];
self.nameLabel.backgroundColor = [UIColor brownColor];
[self.nameLabel setText:#"Test"];
// [self.nameLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.contentView addSubview:self.nameLabel];
...
NSDictionary *viewsDictionary =
NSDictionaryOfVariableBindings(nameLabel, summaryLabel);
NSArray *constraints =
[NSLayoutConstraint constraintsWithVisualFormat:#"|-[nameLabel][summaryLabel]-|"
options:0
metrics:nil
views:viewsDictionary];
I'm fairly new to ios programming and probably don't understand the
view hierarchy as well as I should and thus am failing to successfully
get two labels within a custom table cell class I have created to
autoresize properly. Namely the
"setTranslatesAutoresizingMaskIntoConstraints" property has me a
little confused.
Well, translatesAutoresizingMaskIntoConstraints is a property that is created by Apple to make the transition from Autoresizing (Spring and Struts) to Autolayout easier. Say, you had some AutoresizingMasks for your view and you just switched Autolayout ON without setting any constraints. Then your existing AutoresizingMasks will get converted into constraints which will hold the view in place. So, by default translatesAutoresizingMaskIntoConstraints property is set to be YES. However, when you start adding constraints, in 90% cases they will conflict with the constraints that got created by converting your AutoresizingMasks. So, it is better to turn it off by setting view.translatesAutoresizingMaskIntoConstraints = NO
In your code, the following might have been creating the problems:
The setting of Frame
You should not set frames to objects which you will be adding constraints to. It's a paradigm shift. When you think in Autolayout way, frames are but effects of setting right constraints who combinedly determine the frame of the view in question.
So, please remove the frame setting.
self.nameLabel = [[UILabel alloc] init]; will suffice.
Setting proper constraints
Your Code:
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(nameLabel, summaryLabel);
NSArray *constraints =
[NSLayoutConstraint constraintsWithVisualFormat:#"|-[nameLabel][summaryLabel]-|"
options:0
metrics:nil
views:viewsDictionary];
NameLabel
Now, the above constraints tells us the nameLabel should be horizontally (as you have not mentioned H: or V:) spaced "standard" distance (20px) from container, adjacent to the summaryLabel.
But what about its Y position and Width and Height?
So we need more constraints.
summaryLabel
Same is applicable for summaryLabel.
So, lets define them properly:
NSDictionary *viewsDictionary =
NSDictionaryOfVariableBindings(nameLabel, summaryLabel);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[nameLabel(100)][summaryLabel]-|" options:0 metrics:nil views:viewsDictionary];
NSArray *constraints1 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[nameLabel(30)]" options:0 metrics:nil views:viewsDictionary];
NSArray *constraints2 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[summaryLabel]" options:0 metrics:nil views:viewsDictionary];
NSArray *constraints3 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[nameLabel(==summaryLabel)]" options:0 metrics:nil views:viewsDictionary];
[self.view addConstraints:constraints];
[self.view addConstraints:constraints1];
[self.view addConstraints:constraints2];
[self.view addConstraints:constraints3];
Now your views will look fine.
At any point of time, to check if your views are missing any constraints, pause the debugger and type the following in the console
po [[UIWindow keyWindow] _autolayoutTrace]
it will show which of your views are having AMBIGUOUS LAYOUT
Also, remember in Storyboard/IB if the constraints are showing as "orange" colour, you need more constraints to define the objects position. Once you have added all necessary constraints, the constraints colours turn to "blue"
First, are you adding constraints to self.contentView after creation?
Second, maybe your constraint set is insufficient for autolayout and it creates own constraints based on autoresizing mask. Try to add vertical constraints and width constraints for labels.
I made a few UIScrollView's in different views, they all worked without Autolayout.
I turned Autolayout on, because it was better for my app.
But since then, there's a big problem with my UIScrollView's:
No one is scrolling, they don't work.
Here's my code for a UIScrollView:
.m:
-(viewDidLoad) {
scrollerHome.contentSize = CGSizeMake(320, 1000);
scrollerHome.scrollEnabled = YES;
[self.view addSubview:scrollerHome];
scrollerHome.showsHorizontalScrollIndicator = false;
scrollerHome.showsVerticalScrollIndicator = false;
[super viewDidLoad];
}
.h:
#interface ViewController : UIViewController{
IBOutlet UIScrollView *scrollerHome;
}
Do I have to add some code because I turned on Autolayout?
You should call [super viewDidLoad] before doing anything !
In autolayout, you do not set the contentSize manually. Autolayout works slightly differently with scrollviews, whereby the contentSize of the scroll view is dictated by the constraints of the scrollview's subviews.
If you're trying to force the contentSize to some large size (for example, you're implementing some infinite scroller), you can just add a subview of the appropriate size, e.g.:
UIView *containerView = [[UIView alloc] init];
containerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:containerView];
NSDictionary *views = NSDictionaryOfVariableBindings(containerView);
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[containerView]|" options:0 metrics:nil views:views]];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[containerView(1000)]|" options:0 metrics:nil views:views]];
But if you were trying to set the contentSize in anticipation of adding subviews, you generally don't have to do anything, such as the above snippet. Just add your subviews, provide their constraints, and autolayout will adjust the scroll view's contentSize automatically.
As mentioned above, with autolayout, you can just add the subviews to your scrollview (with their constraints), and the contentSize will be calculated automatically for you.
There is a trick here, though. You sometimes you want to size a subview based upon the dimensions of the screen. But the usual technique of using the | symbols won't work. For example, for an imageview1 inside a scrollview, the usual #"H:|[imageview1]|" won't set the imageview1 to be the width of the screen, but rather it will define the scroll view's contentSize to match the width of imageview1, but it says nothing about what the width of that image view should be!
So, it's useful to capture a reference to the scroll view's superview. That way, you can use something like #"H:|[imageview1(==superview)]|", which not only says "make the scroll view's contentSize equal to the width of imageview1", but also "define the width of imageview1 to be equal to the width of the scroll view's superview."
Thus, for example, to add three images in a paging scroll view, you might do something like:
UIImageView *imageview1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"_DSC0004.jpg"]];
imageview1.contentMode = UIViewContentModeScaleAspectFit;
imageview1.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:imageview1];
UIImageView *imageview2 = ... // configured similar to imageview1
UIImageView *imageview3 = ... // configured similar to imageview1
UIView *superview = self.scrollView.superview;
NSDictionary *views = NSDictionaryOfVariableBindings(imageview1, imageview2, imageview3, superview);
// not only define the image view's relation with their immediate scroll view,
// but also explicitly set the size in relation to the superview, too!
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[imageview1(==superview)][imageview2(==superview)][imageview3(==superview)]|" options:0 metrics:nil views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageview1(==superview)]|" options:0 metrics:nil views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageview2(==superview)]|" options:0 metrics:nil views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageview3(==superview)]|" options:0 metrics:nil views:views]];
self.scrollView.pagingEnabled = YES;
From the Apple iOS 6.0 release notes:
"In general, Auto Layout considers the top, left, bottom, and right edges of a view to be the visible edges. That is, if you pin a view to the left edge of its superview, you’re really pinning it to the minimum x-value of the superview’s bounds. Changing the bounds origin of the superview does not change the position of the view.
The UIScrollView class scrolls its content by changing the origin of its bounds. To make this work with Auto Layout, the top, left, bottom, and right edges within a scroll view now mean the edges of its content view."
You can find the full notes here and find the answer to your question in the section that I quoted from. They give code examples on how to use UIScrollView in a mixed Auto Layout environment.
I'm having troubles with UIScrollView using auto layout constraints.
I have the following view hierarchy, with constraints set through IB:
- ScrollView (leading, trailing, bottom and top spaces to superview)
-- ContainerView (leading, trailing, bottom and top spaces to superview)
--- ViewA (full width, top of superview)
--- ViewB (full width, below ViewA)
--- Button (full width, below ViewB)
The ViewA and ViewB have initial heights of 200 points, but it can be expended vertically to an height of 400 points by clicking on it. ViewA and ViewB are expanded by updating their height constraint (from 200 to 400). Here is the corresponding snippet :
if(self.contentVisible) {
heightConstraint.constant -= ContentHeight;
// + additional View's internal constraints update to hide additional content
self.contentVisible = NO;
} else {
heightConstraint.constant += ContentHeight;
// + additional View's internal constraints update to show additional content
self.contentVisible = YES;
}
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:.25f animations:^{
[self.view layoutIfNeeded];
}];
My problem is that if both views are expanded, I need to be able to scroll to see the whole content, and right now the scroll is not working. How can I manage to update the scroll view using constraints to reflect the changes of ViewA and ViewB heights ?
The only solution I can think of so far is to manually set the height of the ContainerView after the animation, which will be the sum of the heights of ViewA + ViewB + Button. But I believe there is a better solution?
Thanks
I use pure structure like the following
-view
-scrollView
-view A
-view B
-Button
Make sure Button(THE LAST view) has a constraint(vertical spacing from its bottom to superview, which is the scrollview), in this case, no matter what changes for your view A and view B would be, scrollView's height will be changed accordingly.
I reference to this great online book site.
Just read the "Creating a scroll view" section, you should have an idea.
I had the similar problem that I was creating a detail view and using Interface Builder with Auto layout is such a good fit for the task!
Good luck!
(Additional resources:
Stack overflow discussion about the auto layout for scroll view.
iOS 6 has a Release Notes talking about Auto Layout support for UIScrollView.
Free online iOS book explanation about scroll view. This actually helped me a lot!
Let's say we have a hierachy like this (Label1 is a subview of ContentView; ContentView is a subview of ScrollView, ScrollView is a subiview of the viewcontroller's view):
ViewController's View
ScrollView
ContentView
Label1
Label2
Label3
ScrollView is constrained with autolayout in the normal way to the viewcontroller's view.
ContentView is pinned top/left/right/bottom to scrollview. Meaning you have constraints that make the ContentView's top/bottom/leading/trailing edges constrained to be equal to the same edges on the ScrollView. Here is a key: these constraints are for the contentSize of the ScrollView, not its frame size as shown in the viewcontroller's view. So it's not telling the ContentView to be the same frame size as the displayed ScrollView frame, it's rather telling Scrollview that the ContentView is its content and so if contentview is larger than the ScrollView frame then you get scrolling, just like setting scrollView.contentSize larger than scrollView.frame makes the content scrollable.
Here is another key: now you have to have enough constraints between ContentView, Label1-3, and anything else besides the Scrollview for the ContentView to be able to figure out it's width and height from those constraints.
So for example if you want a vertically scrolling set of labels, you set a constraint to make the ContentView width equal to the ViewController View's width, that takes care of the width. To take care of the height, pin Label1 top to ContentView top, Label2 top to Label1 bottom, Label3 top to Label2 bottom, and finally (and importantly) pin Label3's bottom to ContentView's bottom. Now it has enough information to calculate the ContentView's height.
I hope this gives someone a clue, as I read through the above posts and still couldn't figure out how to make the ContentView's width and height constraints properly. What I was missing was pinning the Label3's bottom to the ContentView's bottom, otherwise how could ContentView know how tall it is (as Label3 would just then be floating, and there would be no constraint to tell ContentView where it's bottom y position is).
This is an example of how I have laid out a pure autolayout UIScrollView with a container view. I've commented to make it clearer:
container is a standard UIView and body is a UITextView
- (void)viewDidLoad
{
[super viewDidLoad];
//add scrollview
[self.view addSubview:self.scrollView];
//add container view
[self.scrollView addSubview:self.container];
//body as subview of container (body size is undetermined)
[self.container addSubview:self.body];
NSDictionary *views = #{#"scrollView" : self.scrollView, #"container" : self.container, #"body" : self.body};
NSDictionary *metrics = #{#"margin" : #(100)};
//constrain scrollview to superview, pin all edges
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|" options:kNilOptions metrics:metrics views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:kNilOptions metrics:metrics views:views]];
//pin all edges of the container view to the scrollview (i've given it a horizonal margin as well for my purposes)
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[container]|" options:kNilOptions metrics:metrics views:views]];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-margin-[container]-margin-|" options:kNilOptions metrics:metrics views:views]];
//the container view must have a defined width OR height, here i am constraining it to the frame size of the scrollview, not its bounds
//the calculation for constant is so that it's the width of the scrollview minus the margin * 2
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.container attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeWidth multiplier:1.0f constant:-([metrics[#"margin"] floatValue] * 2)]];
//now as the body grows vertically it will force the container to grow because it's trailing edge is pinned to the container's bottom edge
//it won't grow the width because the container's width is constrained to the scrollview's frame width
[self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[body]|" options:kNilOptions metrics:metrics views:views]];
[self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[body]|" options:kNilOptions metrics:metrics views:views]];
}
In my example 'body' is a UITextView, but it could be anything else. If you happen to be using a UITextView as well note that in order for it to grow vertically it must have a height constraint that gets set in viewDidLayoutSubviews. So add the following constraint in viewDidLoad and keep a reference to it:
self.bodyHeightConstraint = [NSLayoutConstraint constraintWithItem:self.body attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:nil multiplier:1.0f constant:100.0f];
[self.container addConstraint:self.bodyHeightConstraint];
Then in viewDidLayoutSubviews calculate the height and update the constraint's constant:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self.bodyHeightConstraint setConstant:[self.body sizeThatFits:CGSizeMake(self.container.width, CGFLOAT_MAX)].height];
[self.view layoutIfNeeded];
}
The second layout pass is needed to resize the UITextView.
Use this code. ScrollView setContentSize should be called async in main thread.
Swift:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
DispatchQueue.main.async {
var contentRect = CGRect.zero
for view in self.scrollView.subviews {
contentRect = contentRect.union(view.frame)
}
self.scrollView.contentSize = contentRect.size
}
}
Objective C:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
dispatch_async(dispatch_get_main_queue(), ^ {
CGRect contentRect = CGRectZero;
for(UIView *view in scrollView.subviews)
contentRect = CGRectUnion(contentRect,view.frame);
scrollView.contentSize = contentRect.size;
});
}
At every moment the scroll view should know its content size. The content size is inferred from the scrollview's subviews. It is very handy to map controller properties to the constraints in the xib file describing heights of the subviews. Then in the code (an animation block) you can just change constants of these constraint properties. If you need to change the entire constraint, keep a reference to it, so that you can update it later in the parent container.
My variant for scroll view with !Dynamic! height:
1) Add scroll view to your UIView. Pin all (top, bottom, lead, trail) constraints.
2) Add UIView to Scroll View. Pin all (top, bottom, lead, trail) constraints. It will be your Content view. You can also rename it.
3) Control drag from Content view to Scroll view - Equal width
4) Add content to your UIView. Set needed constraints. And! At the lower item add bottom constraint NOT Greater or equal (>=)(Like most people talks) BUT Equal! Set it to 20 for example.
In my situation I have UIImageView in content. I have connected it's height to code. And if I change it to like 1000, scroll is visible. And all works.
Works like a charm for me. Any questions - welcome to comments.