ios 使用具有动态高度的子视图自动布局 UIScrollView

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16825189/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 23:43:06  来源:igfitidea点击:

Auto layout UIScrollView with subviews with dynamic heights

iosobjective-cuiscrollviewautolayout

提问by lukas

I'm having troubles with UIScrollView using auto layout constraints. I have the following view hierarchy, with constraints set through IB:

我在使用自动布局约束时遇到了 UIScrollView 的问题。我有以下视图层次结构,通过 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 :

ViewA 和 ViewB 的初始高度为 200 点,但可以通过单击垂直扩展到 400 点的高度。ViewA 和 ViewB 通过更新它们的高度约束(从 200 到 400)来扩展。这是相应的片段:

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 ?

我的问题是,如果两个视图都展开,我需要能够滚动以查看整个内容,而现在滚动不起作用。如何使用约束来更新滚动视图以反映 ViewA 和 ViewB 高度的变化?

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?

目前我能想到的唯一解决方案是在动画后手动设置ContainerView的高度,即ViewA+ViewB+Button的高度之和。但我相信有更好的解决方案吗?

Thanks

谢谢

回答by lorixx

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.

确保 Button( THE LAST view) 有一个约束(从底部到超级视图的垂直间距,即滚动视图),在这种情况下,无论视图 A 和视图 B 有什么变化,滚动视图的高度都会相应地改变.

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!

我遇到了类似的问题,我正在创建一个详细信息视图,并且使用带有自动布局的 Interface Builder 非常适合这项任务!

Good luck!

祝你好运!

(Additional resources:

(其他资源:

Stack overflow discussion about the auto layout for scroll view.

关于滚动视图自动布局的堆栈溢出讨论。

iOS 6 has a Release Notestalking about Auto Layout support for UIScrollView.

iOS 6 有一个发行说明,讨论了对 UIScrollView 的自动布局支持。

Free online iOS book explanationabout scroll view. This actually helped me a lot!

关于滚动视图的免费在线iOS 书籍解释。这实际上帮助了我很多!

回答by Doug Voss

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):

假设我们有这样的层次结构(Label1 是 ContentView 的子视图;ContentView 是 ScrollView 的子视图,ScrollView 是视图控制器视图的子视图):

ViewController's View ScrollView ContentView Label1 Label2 Label3

ViewController's View ScrollView ContentView Label1 Label2 Label3

ScrollView is constrained with autolayout in the normal way to the viewcontroller's view.

ScrollView 以视图控制器视图的正常方式受到自动布局的约束。

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.

ContentView 固定在滚动视图的顶部/左侧/右侧/底部。这意味着您有约束,使 ContentView 的顶部/底部/前导/后缘被约束为等于 ScrollView 上的相同边缘。这里有一个关键:这些约束是针对 ScrollView 的 contentSize,而不是它在 viewcontroller 的视图中显示的帧大小。所以它不是告诉 ContentView 与显示的 ScrollView 框架具有相同的框架大小,而是告诉 Scrollview ContentView 是它的内容,所以如果 contentview 大于 ScrollView 框架,那么你会滚动,就像设置 scrollView.contentSize 更大比 scrollView.frame 使内容可滚动。

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.

这是另一个关键:现在您必须在 ContentView、Label1-3 和除 Scrollview 之外的任何其他内容之间有足够的约束,以便 ContentView 能够从这些约束中找出它的宽度和高度。

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.

因此,例如,如果您想要一组垂直滚动的标签,您可以设置一个约束,使 ContentView 宽度等于 ViewController 视图的宽度,这会处理宽度。为了处理高度,将 Label1 顶部固定到 ContentView 顶部,Label2 顶部固定到 Label1 底部,Label3 顶部固定到 Label2 底部,最后(并且重要的是)将 Label3 的底部固定到 ContentView 的底部。现在它有足够的信息来计算 ContentView 的高度。

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).

我希望这能给某人一个线索,因为我阅读了上述帖子,但仍然无法弄清楚如何正确设置 ContentView 的宽度和高度约束。我缺少的是将 Label3 的底部固定到 ContentView 的底部,否则 ContentView 怎么知道它有多高(因为 Label3 那时会浮动,并且没有约束告诉 ContentView 它的底部 y 位置在哪里)。

回答by jwswart

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:

这是我如何使用容器视图布置纯自动布局 UIScrollView 的示例。我已经发表评论以使其更清楚:

container is a standard UIView and body is a UITextView

容器是一个标准的 UIView,body 是一个 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:

在我的示例中,'body' 是一个 UITextView,但它可以是其他任何东西。如果您碰巧正在使用 UITextView,请注意,为了使其垂直增长,它必须具有在 viewDidLayoutSubviews 中设置的高度约束。因此,在 viewDidLoad 中添加以下约束并保留对它的引用:

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:

然后在 viewDidLayoutSubviews 中计算高度并更新约束的常量:

- (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.

需要第二个布局通道来调整 UITextView 的大小。

回答by Karen Hovhannisyan

Use this code. ScrollView setContentSize should be called async in main thread.

使用此代码。ScrollView setContentSize 应在主线程中异步调用。

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:

目标 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;
                       });
}

回答by Vlad Pulichev

My variant for scroll view with !Dynamic! height:

我的滚动视图变体!Dynamic!高度:

1) Add scroll viewto your UIView. Pin all (top, bottom, lead, trail) constraints.

1) 添加scroll view到您的UIView. 固定所有(顶部、底部、引线、尾迹)约束。

2) Add UIViewto Scroll View. Pin all (top, bottom, lead, trail) constraints. It will be your Content view. You can also rename it.

2) 添加UIViewScroll View. 固定所有(顶部、底部、引线、尾迹)约束。它将是您的Content view. 您也可以重命名它。

3) Control drag from Content viewto Scroll view- Equal width

3)控制拖动从Content viewScroll view-Equal width

4) Add content to your UIView. Set needed constraints. And! At the lower item add bottom constraint NOTGreater or equal(>=)(Like most people talks) BUTEqual! Set it to 20for example.

4) 将内容添加到您的UIView. 设置所需的约束。和!在较低的项目添加底部约束NOTGreater or equal( >=)(就像大多数人所说的那样)但是Equal!将其设置20为例如。

In my situation I have UIImageViewin content. I have connected it's heightto code. And if I change it to like 1000, scroll is visible. And all works.

在我的情况下,我有UIImageView内容。我已将其连接height到代码。如果我将其更改为 like 1000,则滚动可见。和所有的作品。

Works like a charm for me. Any questions - welcome to comments.

对我来说就像一种魅力。任何问题 - 欢迎评论。

回答by Vladimír Slavík

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.

每时每刻滚动视图都应该知道它的内容大小。内容大小是从滚动视图的子视图中推断出来的。将控制器属性映射到描述子视图高度的 xib 文件中的约束非常方便。然后在代码(动画块)中,您可以更改这些约束属性的常量。如果需要更改整个约束,请保留对它的引用,以便稍后可以在父容器中更新它。