11

我试图了解 UIScrollView 在自动布局环境中的工作原理。到目前为止,我已经尝试阅读 Apple 文档、Stack Overflow 研究、Google 研究,并研究了 Matt Neuberg 的工作示例。

Apple 6.1 文档说:

UIScrollView 对象(或者简单地说,滚动视图)的核心概念是它是一个视图,其原点可在内容视图上调整。它将内容剪辑到其框架中,通常(但不一定)与应用程序主窗口的内容一致。滚动视图跟踪手指的移动并相应地调整原点。“通过”滚动视图显示其内容的视图根据新原点绘制自身的该部分,该原点固定到内容视图中的偏移量。除了显示垂直和水平滚动指示器外,滚动视图本身不绘图。滚动视图必须知道内容视图的大小,以便知道何时停止滚动;默认情况下,当滚动超出内容范围时,它会“弹回”。

基于此,让我们来看看应该如何设置约束。

为了更容易讨论,假设我们有 3 个视图作为一般情况 - 默认的主视图控制器视图 (A),它的子视图是 UIScrollview (B),而 UIScrollview 有一个子视图,它是 UIView (C )。假设我们希望 (C) 高 1000 个单位。

所以我们进入界面构建器,在故事板中选择视图控制器,然后在属性检查器选项卡上将大小更改为自由格式。对于视图 (A)、(B) 和 (C),我们在尺寸检查器选项卡上将高度更改为 1000。

(A) 和主窗口之间的约束

是时候设置约束了。该文档明确指出“(滚动视图)将内容剪辑到其框架中,通常(...)与应用程序主窗口的内容一致”。在我们的示例中,(A) 将与应用程序主窗口重合,并且对此不需要任何约束。

(A) 和 (B) 之间的约束

现在文档清楚地表明 (B) 与 (A) 完全一致,因此我们将在它们之间设置 4 个约束,前导空格、尾随空格、顶部空间和底部空间以超级查看所有常量为 0。

(B) 和 (C) 之间的约束

文档在这里不是那么简单。它说(B)的原点可以在(C)上调整,所以(B)肯定应该小于(C)。由于我们知道滚动只会上下滚动,因此我们可以将 (B) 和 (C) 之间的左右边缘限制为零,并且我们始终希望它位于中间,因此我们将添加一个中心x 对齐。我们将在它们之间添加 3 个约束,前导空格和尾随空格以常量 0 和中心 x 对齐到超级视图。为了定位视图,我们需要顶部和底部的东西,老实说,我不确定应该如何根据文档设置这些约束。基于模仿马特纽伯格的例子我让它们的顶部空间以常数为零进行超级视图,底部空间以默认情况下生成的任何常数进行超级视图。这个到superview约束的底部空间是特殊的(显然),从现在开始它应该被称为“specialConstraint”。

所以呢?!我们从说 (B) 绝对应该小于 (C) 开始这一段,并通过设置约束以使它们的大小完全相同来结束它。问题 1 - 为什么会这样?

(C) 本身的约束

我们知道 (C) 必须大于 (B) 以便 (B) 有东西可以滚动,并且 (C) 应该根据自己的约束来确定它的大小。很简单,我们设置了 1 个约束,高度 = 1000。

特殊约束

现在我们在视图控制器中为 specialConstraint 创建一个 outlet,并在 viewDidLoad 方法中设置 self.specialConstraint.constant = 0; 这意味着内容视图的底部应该完全固定在滚动视图的底部,这样您就不会向下滚动。但这在 Matt Neuberg 的例子中是有效的。问题 2 - 为什么会这样?

当 Scrollview 滚动时真正发生了什么

我认为合乎逻辑的做法是固定滚动视图的框架,并且内容视图的原点(或内容偏移量)随着滚动而移动,并且内容像窗口一样显示滚动视图。类似于通过滑动纸张通过墙上的孔查看纸张。

但是,根据我的阅读,滚动视图的框架实际上是在移动,就像固定页面上的放大镜一样。

问题 3 - 有人可以解释一下滚动视图滚动时发生了什么,哪个对象的框架原点实际上正在改变,为什么会这样?

问题 4 - 谁能用简单的英语解释如何在 (A)、(B) 和 (C) 之间设置约束?

4

3 回答 3

0

Working example

Hi. At first, here is working code scroll view example writed couple of days ago.

[self addSubview:scrollView];

id views = NSDictionaryOfVariableBindings(scrollView);
[self addConstraints:PVVFL(@"H:|[scrollView]|").withViews(views).asArray];
[self addConstraints:PVVFL(@"V:|[scrollView]|").withViews(views).asArray];

Constraints writed in code and with help of Parus lib.

As you can mention, scroll view is tied to it's superview. In another words, it fill it completely.

Couple of lines earlier i setup an scrollView tree.

id views = NSDictionaryOfVariableBindings(photoView, storeNameLabel, selectStoreButton, tagsLabel, tagsContainer, editTagsButton, commentView, sendButton, cancelButton);
[scrollView addConstraints:PVVFL(@"V:|-15-[photoView(150)]").withViews(views).asArray];
[scrollView addConstraints:PVVFL(@"|-15-[photoView]-15-|").withViews(views).asArray];
[scrollView addConstraint:PVCenterXOf(photoView).equalTo.centerXOf(scrollView).asConstraint];

[scrollView addConstraint:PVTopOf(storeNameLabel).equalTo.bottomOf(photoView).plus(20).asConstraint];
[scrollView addConstraints:
 PVVFL(@"|-15-[storeNameLabel]-(>=15)-[selectStoreButton]-15-|")
 .alignAllBaseline.withViews(views).asArray];
[selectStoreButton setContentCompressionResistancePriority:UILayoutPriorityRequired
                                                   forAxis:UILayoutConstraintAxisHorizontal];

[scrollView addConstraints:
 PVVFL(@"V:[storeNameLabel]-15-[tagsLabel][tagsContainer]").alignAllLeft.withViews(views).asArray];
[scrollView addConstraint:PVRightOf(tagsContainer).equalTo.rightOf(selectStoreButton).asConstraint];
[scrollView addConstraint:PVTopOf(editTagsButton).equalTo.bottomOf(tagsContainer).plus(10).asConstraint];
[scrollView addConstraint:PVWidthOf(editTagsButton).equalTo.widthOf(tagsContainer).asConstraint];
[scrollView addConstraint:PVLeftOf(editTagsButton).equalTo.leftOf(tagsContainer).asConstraint];

[scrollView addConstraint:PVTopOf(commentView).equalTo.bottomOf(editTagsButton).plus(10).asConstraint];
[scrollView addConstraint:PVWidthOf(commentView).equalTo.widthOf(editTagsButton).asConstraint];
[scrollView addConstraint:PVLeftOf(commentView).equalTo.leftOf(editTagsButton).asConstraint];
[scrollView addConstraint:PVHeightOf(commentView).moreThan.constant(100).asConstraint];
[scrollView addConstraint:PVLeftOf(cancelButton).equalTo.leftOf(commentView).asConstraint];
[scrollView addConstraints:PVVFL(@"[cancelButton]-(>=15)-[sendButton(==cancelButton)]").alignAllBaseline.withViews(views).asArray];
[scrollView addConstraint:PVRightOf(sendButton).equalTo.rightOf(commentView).asConstraint];
[scrollView addConstraints:PVVFL(@"V:[commentView]-10-[cancelButton]-10-|").withViews(views).asArray];

How you can see, this is pretty complicated layout. But key points of it: Internal views have fixed heights, provided by theirs intrinsic content sizes. Combined all together they tried to increase height of scrollView. But scrollView height is fixed by self.view!!! Constraint solver must generate error. But,

Theory

Apple has special Tech note for this case. And there is some interesting moment:

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.

Also this note provide some examples of different approaches when working with scroll views and auto-layouts.

Now i will try to answer your questions.

Answers

Question 1 - Why is this?

As you can read above, this is because auto-layout for scroll view setup content view sizes and not frame sizes.

Question 2 - why is this?

Again, thing is in content size. Special constraint approach looks like hack over interface builder. At xib you have normal size view, and after nib-file instantiating you just back things to normal, fixing this constraint to 0, which mean that content size of scroll view will be equal to C view.

Question 3 - Can someone please explain what's happening when a scrollview scrolls, which object's frame origin is actually changing, and why is it done this way?

Great explanation of this process you can find at this article: Understanding Scroll Views, but short answer is: UIScroll view perform scrolling by changing self.bounds.origin point.

Question 4 - Can anyone explain how the constraints should be set between (A), (B), and (C) in plain English?

You should set constraints inside the scroll view by fixing all edges to internal view. In another words, all edges of scroll view must be fixed twice: By internal views and by superview.

If answer on this question are not clear yet, you can read this answer from very beginning.

于 2013-08-23T13:36:35.460 回答
0

我发现了很多如何做到这一点的例子,但没有一个解释如何在运行时设置 contensize。所以用一个按钮。

实际上,这一切都归结为您的滚动视图内容和滚动视图之间的关系。

现在,您无需代码即可在情节提要中进行滚动视图滚动和调整大小。只有你必须做对。

要使用按钮(在运行时)调整 contensize,您需要调整内容的约束。

这很难理解。我已将代码放在 github http://goo.gl/qrXANp上,您可以在这里找到有关它的视频... https://www.youtube.com/watch?v=4oCWxHLBQ-A

于 2014-02-20T18:03:05.687 回答
0

解决这个神秘问题的基本技巧是......在滚动视图中嵌入另一个视图并设置如下约束

UIView --> UIScrollView -->ContentView

并且您可以在运行时继续向 contentView 添加子视图,并且 uiscrollview 内容大小将自动增加

   self.ContentView = [[UIView alloc] initWithFrame:CGRectZero];
    self.ContentView.translatesAutoresizingMaskIntoConstraints = NO;
    self.scrollview = [[UIScrollView alloc] initWithFrame:CGRectZero];
    self.scrollview.translatesAutoresizingMaskIntoConstraints = NO;
    [self.scrollview sizeToFit];

    [self.scrollview addSubview:self.ContentView];
    [self.view addSubview:self.scrollview];

    NSMutableDictionary *views = [[NSMutableDictionary alloc] init];

    [views setValue:self.ContentView forKey:@"ContentView"];
    [views setValue:self.scrollview forKey:@"scrollview"];
    [views setValue:self.view forKey:@"mainView"];

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[scrollview]-0-|"
                                                                             options:0
                                                                             metrics:nil
                                                                               views:views]];

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[scrollview]-0-|"
                                                                             options:0
                                                                             metrics:nil
                                                                               views:views]];

    [self.scrollview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[ContentView]|"
                                                                             options:0
                                                                             metrics:nil
                                                                               views:views]];

    [self.scrollview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[ContentView]|"
                                                                             options:0
                                                                             metrics:nil
                                                                               views:views]];

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[ContentView(==mainView)]"
                                                                      options:0
                                                                      metrics:nil
                                                                        views:views]];

    self.view.contentMode = UIViewContentModeRedraw;

    [self.view setNeedsUpdateConstraints];
于 2015-07-31T12:55:57.543 回答