Here is my structure of views for this detail view (blogging application, I want to view the entire post which has dynamic height inside of a scrollview):
UIView
-UIScrollView
-SinglePostView (custom view)
-Title
-Subtitle
-Content
Normally to add a 'single post view' to a UIView I simply instantiate it, feed it my Post object and then add a single constraint that pins the width of the singlePostView to the superview, at which point things get laid out nicely. However when I try to add it to a scroll view, it doesn't show up nor does it scroll.
UIScrollView *scrollView = [[UIScrollView alloc] init];
[self.view addSubview:scrollView];
NOBSinglePostView *singlePost = [[NOBSinglePostView alloc] initWithPost:self.post];
[scrollView addSubview:singlePost];
singlePost.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(scrollView,singlePost);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[singlePost]|" options:0 metrics: 0 views:viewsDictionary]];
In the structure you are presenting, using AutoLayout, the contentSize
of your UIScrollView
is driven by the intrinsicContentSize
of its subviews, here your SinglePostView
.
The problem is, SinglePostView
being a subclass of UIView
, its intrinsicContentSize
is always CGSizeZero
when considered by itself. What you need to do is make the intrinsicContentSize
of your SinglePostView
depend on the intrinsicContentSize
of its subviews.
Then, because the subviews of your SinglePostView
are UILabels
, and because a UILabel
's intrinsicContentSize
is the smallest size it needs to display its content, your SinglePostView
's intrinsicContentSize
will be equal to the sum of its subviews intrinsicContentSizes
, that is the total size needed to display the content of all three of your labels.
Here is how to do it.
Step 1: Removing all automatically set constraints
First, as you partially did, you need to remove all constraints automatically set by the system.
Assuming you don't have any constraints set in your storyboard or XIB (or you don't even have one of these), just do:
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
singlePost.translatesAutoresizingMaskIntoConstraints = NO;
titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO;
contentLabel.translatesAutoresizingMaskIntoConstraints = NO;
Now you have a clear slate and you can start setting your own constraints.
Step 2: Constraining the scrollView
First, let's create, as you did, the views references dictionary for AutoLayout to use:
NSDictionary *views = NSDictionaryOfVariableBindings(scrollView, singlePost, titleLabel, subtitleLabel, contentLabel);
Then, also as you already did, let's constrain the scroll view to be the size of its superview:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[scrollView]-0-|" options:0 metrics:0 views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[scrollView]-0-|" options:0 metrics:0 views:views]];
Step 3: Constraining the SinglePostView
to push the scrollView
's contentSize
For this step to be clear, you have to understand that every constraints set between a UIScrollView
and its subviews
will actually change the contentSize
of the UIScrollView
, not its actual bounds
. For Example, if you constrain a UIImageView
to the borders of its parent UIScrollView
and then put an image twice the size of the UIScrollView
inside the UIImageView
, your image won't get shrunk, its the UIImageView
that will take the size of its image and become scrollable inside the UIScrollView
.
So here is what you have to set here:
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[singlePost]-0-|" options:0 metrics:0 views:views]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[singlePost]-0-|" options:0 metrics:0 views:views]];
[scrollView addConstraint:[NSLayoutConstraint constraintWithItem:singlePost
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:scrollView
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0.0f]];
First two constraints are pretty obvious. The third one, however, is here because, for your UILabels
to display their content properly and still be readable, you will probably want them to be multilined and the scrolling to be vertical, not horizontal. That's why you set your SinglePostView
's width
to be the same as your scrollView
's. This way, you prevent your scrollView
's contentSize.width
to be anything more than its bounds.width
.
Step 4: Constraining your UILabels
to "push" the bounds of your SinglePostView
Fourth and final step, you now need to set constraints on your SinglePostView
's subviews, so that it gets an intrinsicContentSize
from them.
Here is how you do it (simplest implementation, no margins, one label after the other vertically):
[singlePost addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[titleLabel]-0-|" options:0 metrics:0 views:views]];
[singlePost addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[subtitleLabel]-0-|" options:0 metrics:0 views:views]];
[singlePost addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[contentLabel]-0-|" options:0 metrics:0 views:views]];
[singlePost addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[titleLabel]-0-[subtitleLabel]-[contentLabel]-0-|" options:0 metrics:0 views:views]];
And with that you should be done.
One last advice, you should definitely look into UIStoryboard
to do these kinds of things. It's a lot simpler and more visual.
Hope this helps,
P.S.: If you want, I can take some time and push a project using both UIStoryboard
and the Visual Format Language
on Github. Just tell me if you would need one.
Good luck.
in auto layout
frame of scrollview
is decided by constraints between scrollview
and superview
of scrollview
.
contentSize
of scrollview
is decided by constraints between scrollview
and subview
of scrollview
.
you should set the size of singlePostView
. ScrollView
calculate contentSize
from it. (you need to add size constraints explicitly)
CGFloat heightOfSinglePostView = calculate..
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[singlePost(heightOfSinglePostView)]|" options:0 metrics: 0 views:viewsDictionary]];