Adding a dynamically sized view (height) to a UISc

2019-02-15 14:35发布

问题:

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]];

回答1:

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.



回答2:

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]];