如何使用 Autolayout 设置 UIScrollview 的约束?

我已经花了两天时间试验了 混合和纯自动布局方法的各种解决方案,以实现在自动布局之前的一个微不足道的滚动视图设置,现在它是正式的-我一定是太愚蠢了。我主要是在 Storyboard 中设置这个(好吧,就是这样)。

所以我请求你们的帮助。

视图树:

UIView
-UIView
-UIView
..-UIScrollview
...-UIButton
...-UIButton
...-UIButton

按钮应该是水平滚动的(从左到右,反之亦然)。有人能让我知道如何设置约束,以实现这使用纯自动布局? ? ?

--

我尝试了混合方法,就像这样:

UIView
- UIView
- UIView
..-UIScrollview
...-UIView (contentview)
....-UIButton
....-UIButton
....-UIButton

根据苹果的 TechNote 设置 contentviewtranslatesAutoresizingMaskIntoConstraints的宽度和高度限制。按钮和 scrollview 是使用约束设置的。这使得 scrollview 滚动(耶) ,但是唉,它滚动得太远了!据我所知,滚动宽度比我在内容视图中设置的宽度增加了一倍?

我也尝试了纯自动布局的方法,既有 contentview也有没有。视图是 translatesAutoresizingMaskIntoConstraints=NO,除了 self.view。按钮具有固定的宽度/高度约束,并且固定在 scrollview 的所有四条边上。没有卷轴。

所以我完全不明白为什么我不能让它正常工作。任何帮助都是非常感谢的,如果你需要任何其他信息,请问!

更新的解决方案截图- ButtonZ 约束:

enter image description here

编辑@Jamie Forrest 因此,最后一个按钮的尾随约束是错误的。我设置的值是负的,而不是 -6441。棘手的是,当在故事板中设置值时,在 Pin 工具栏中有两个选项:

enter image description here

当前画布值为负(导致无滚动) ,下面的选项为正(激活滚动)。这意味着我不笨,但至少是半盲的。不过,容我辩解一下,XCode 没有显示“不正确”设置的错误,这是不是有点令人不安呢?

再次编辑 现在这是有趣的... 改变尾随值从 -6441(没有滚动)到6441启用滚动。但是我的老朋友“内容太多”又回来了,导致内容的大小是原来的两倍!获得正确内容滚动的解决方案是将尾随约束设置为 ZERO!在使用 Storyboard 时,这一点并不明显,但是看看@Infinity James 的代码,它应该是这样的。

90812 次浏览

ContentSize 是通过应用 UIScrollView 内部的约束隐式设置的。

例如,如果你在 UIView 内部有一个 UIScrollView,它看起来会是这样的(我相信你是知道的) :

    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

这将设置 scrollView 以填充容器视图的大小(因此容器视图必须具有一定的大小)。

然后,您可以通过隐式地将 UIScrollView 的 contentSize 设置为足够大来容纳按钮,如下所示:

    UIButton *buttonA                   = [[UIButton alloc] init];
UIButton *buttonB                   = [[UIButton alloc] init];
UIButton *buttonC                   = [[UIButton alloc] init];
[scrollView addSubview:buttonA];
[scrollView addSubview:buttonB];
[scrollView addSubview:buttonC];
buttonA.translatesAutoresizingMaskIntoConstraints       = NO;
buttonB.translatesAutoresizingMaskIntoConstraints       = NO;
buttonC.translatesAutoresizingMaskIntoConstraints       = NO;


viewsDictionary                     = NSDictionaryOfVariableBindings(scrollView, buttonA, buttonB, buttonC);


[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[buttonA]-|"
options:kNilOptions
metrics:nil
views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[buttonA]-[buttonB]-[buttonC]-|"
options:NSLayoutFormatAlignAllBaseline
metrics:nil
views:viewsDictionary]];

纯粹的自动布局方法工作得很漂亮,但是如果您从非自动布局迁移,那么设置它是一件相当痛苦的事情。我已经做过几次了,我有一些基本的建议:

  • 从小处着手: 即使这意味着要重新创建你的故事板视图,也要从一些元素开始,慢慢地构建你的视图,确保在添加了一些元素之后测试滚动是否有效。
  • 关闭所有约束的自动调整大小: 这对我来说一直是约束冲突的原因。
  • 正确设置您的 UIScrollView 约束: 确保滚动视图在所有方面都连接到父视图,否则它根本不会展开。

我想你的 contentSize出了点问题。看看 这篇博文如何处理使用“纯”自动布局方法时的 contentSize。其要点是您的约束隐式地定义了内容大小。在使用自动布局时,永远不要显式地设置它。我在博客文章的末尾附上了一个示例项目来演示它是如何工作的

在这里粘贴约束时,很难看到确切的值和约束设置,所以我不确定看了屏幕截图后哪里出了错。

我没有解释设置中出了什么问题,而是创建了一个基本的 样本项目,其视图层次结构和约束设置与您描述的非常相似。在样例项目中,水平滚动按预期工作,它使用了 Apple 在 技术说明中描述的“纯自动布局”方法。

我也有很多麻烦,最初让自动布局与 UIScrollView的工作。使其工作的关键是确保所有的项目在滚动视图中,一起,有约束,最终链接到滚动视图的所有方面,并有助于自动布局系统能够确定一个 contentSize 的滚动视图,将大于其框架。看起来您正在尝试在您的代码中这样做,但是也许您有一些多余的约束,使得 contentSize 太小。

另外值得注意的是,正如其他人提到的,使用 AutoLayout 和 UIScrollview,您不再显式地设置 contentSize。AutoLayout 系统根据约束计算 contentSize。

我还发现 这个电子书的章节对我理解这些工作原理很有帮助。希望这些能帮到你。

技术记录里有一段你可能看过了。可以使用固定在滚动视图边缘的约束隐式设置滚动视图的内容大小。

这里有一个简单的例子。创建一个只有一个视图的故事板,这个视图只有一个滚动视图。设置滚动视图约束,使其适合放入的视图的大小。

在滚动视图中添加单个视图。使用约束显式设置视图的大小(并确保大小大于滚动视图)。

现在向该内部视图添加四个约束,将内部视图的四条边锁定到其父滚动视图。这四个约束将导致内容大小扩展以适应内部视图。

如果你有多个视图想要添加到一个滚动视图中,例如水平布局,你可以将第一个子视图的左侧锁定在滚动视图的左侧,将各个子视图水平地锁定在一起,将最后一个子视图的右侧锁定在滚动视图的右侧。这些约束将迫使滚动视图的内容大小扩展以适应所有子视图及其约束。

我知道这是一个外行人的解决方案,而不是苹果在文档中建议的那样,但它为我工作了两次,内容不同,而且可以很快设置: 在故事板视图控制器中插入 UIView。 在 UIView 中插入表视图,动态,0原型单元格,样式平原或分组。 在“表视图”中插入一个滚动视图, 在滚动视图中插入内容。 就是这样,自定义视图控制器中没有设置。

关于在 UIScrollView 中使用 AutoLayout 有很多问题,我们忽略的关键点是 UIScrollView 的内部视图对 内容视图有约束,而对 UIScrollView 本身没有约束。参考 技术说明 TN2154,你可以找到:

UIScrollView 类通过更改其边界的原点来滚动其内容。为了在自动布局中实现这一点,滚动视图中的顶部、左侧、底部和右侧边缘现在意味着其内容视图的边缘。

下图显示: enter image description here

您可以发现尾随空间是500点,如果约束是对 UIScrollView,视图将错过放置,应该更新其框架。但是,没有警告也没有错误。因为所有的约束都是针对内容视图的。

UIScrollView 将根据内部视图的约束计算内容视图的大小。(例如,内容大小: width = 100(前导空间) + 200(视图的宽度) + 500(尾随空间) ,height = 131(顶部间距) + 200(高度) + 269(底部间距)

如何在 UIScrollView 中为视图添加约束:

  1. 在内容视图中显示视图的位置。
  2. 向内容视图的边缘添加顶部、右侧、底部、左侧间距,此外,还要添加这些视图的宽度和高度。

一切都结束了。

使用 scrollview 处理 AutoLayout 的一个简单方法是添加一个容器视图,其中包含滚动视图中的所有子视图。

结论: 理解 UIScrollView 自动布局的关键是内部视图对内容视图进行约束,而不是 UIScrollView 本身。

连接 示例代码

哈哈,欢迎加入愚蠢俱乐部。我是创始人之一

对于垂直滚动 : 我能让它工作的唯一方法(iOS 8,Xcode 6和纯自动布局)是在我的滚动视图中添加以下约束条件(所有这些条件都与超视图相关) :

  • 等宽
  • 平等高度
  • 中央 Y 对准
  • 中心 X 对准

我的结构:

  UIView
- ScrollView
- Subview
- Subview
- Subview
- Subview
- ...

这是最终的结果:

Demo

这是设置:

Setup 全屏

这就是这个项目。

希望这能让某人免于在凌晨5点入睡

如果你的问题是“如何在垂直滚动的 UIScrollView 中放入一些 UITextFields,以便当它们聚焦时能够移出键盘”,那么最好的答案是:

不要。

对静态单元格使用 UITableViewController。

如果您的视图控制器显示在 UINavigationController 中,那么您可以免费获得这种滚动出局行为,并且所有内容都插入 Just Work。

处理这个问题一段时间后,我终于找到了解决办法。我正在使用通用类大小的故事板(600x600)。我创建了一个 scrollView 大小的 UIView (contentView) ,并创建了对 scrollView 的顶部、底部、前导和尾随的约束。然后我手动将 contentView 的大小修剪为600x600。故事板停止试图调整大小的一切,我可以工作,但看起来糟糕的真实设备或模拟器。 我制作了两个约束出口的这个裁剪大小。

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *contentViewWidthConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *contentViewHeightConstraint;

然后是 viewDidLoad

CGSize viewSize = self.view.frame.size;
self.contentViewWidthConstraint.constant = viewSize.width;
self.contentViewHeightConstraint.constant = viewSize.height;

效果很好。

如果你像我一样,只是在子视图中使用静态内容而不受限制,你可以这样做:

override func viewDidLayoutSubviews() {
scrollView.contentSize = CGSizeMake(320, 800)
}

我花了几天的时间试图找到一个解决方案,如何使用 AutoLayout 视图嵌入式 Scrollview,将 Scrollview 放在可见屏幕的中心,它可以跨越所有设备/屏幕尺寸以及屏幕旋转。

我花了几天的时间试着只用自动布局来做这件事,并且已经很接近了,但是还是不够接近。所以最后我不得不在 viewDidLoad 中为每个屏幕添加3行代码。

见下面的解决方案:

  1. 创建 scrollview 并用您想要的任何对象填充它
  2. 打开自动布局
  3. 然后使滚动视图垂直和水平居中 Centre ScrollView
  4. 选择 观景,然后选择 ‘添加缺失的约束’-这样就可以了
  5. 其结果是产生了大量的约束。有两个新的创建视图:’Horiz 空间滚动视图查看’和’垂直空间滚动视图查看’或反之亦然。
  6. 删除“ Horiz 空间滚动视图到视图”,这样你现在在视图上有3个约束。2用于在视图中输入 scrollview,2用于在 scrollview 和视图之间设置垂直空间
  7. 现在,通过单击并用 Ctrl 将 Vert 约束拖动到头文件并创建一个 NSLayoutConstraint IBOutlet (我称之为 mine straintVertVtoSV) ,将它链接到您的代码
  8. 现在去。M 文件,并将这些代码行添加到 viewDidLoad (使用填充量以获得正确的视点居中)

    if (IPAD)
    

    { 常量 = 150.0; }

  9. 现在这应该运行在所有设备上,并正确居中,仍然正确滚动。

下面的解决方案对我适用于 带自动布局且不带 contentSize 的 scrollView:

  1. 将 scrollView 拖放到 viewController 中,并应用任何约束来覆盖所需的空间。
  2. 在 scrollView 中拖放一个 UIView,使其覆盖 scrollView 的整个空间,并从 scrollView 应用 约束为顶部,左,右,底部空间
  3. 根据滚动的需要设置内部视图的 身高(如果需要水平滚动则设置宽度)。如果需要,也可以通过代码完成此部分。
  4. 危急情况。在点(3)中将高度设置为较大值后,返回到点(2) ,并确保设置顶部、左边、右边和底部的值 回到零,因为当您强制更改点(3)中的高度时,Xcode 可能已经为您更改了这些值。

你完蛋了。现在,您可以在此视图上添加任意数量的控件,并应用彼此相关的约束(如果没有此视图,这些约束似乎无法工作)。如果您不想使用这个视图,那么您必须为每个与 scrollView 相关的控件应用约束(彼此不相关)。


压倒性的小费。

临界 。为了清晰起见,我们假设 UIScrollView 是 1000英尺宽和100高。(事实上,通常这些值是动态的,当然,这取决于设备的宽度等。但现在只能说1000宽和100高。)假设你在做一个水平滚动。因此,在 UIScrollView 中放置一个 UIView。(这就是“内容视图”。)将内容视图的顶部、底部、前部、尾部的所有四个约束设置为滚动视图。让他们都为零,即使这似乎是错误的。将内容 UIView 的高度设置为100,然后忘记这一点。现在: 你想要水平滚动,所以设置内容视图的宽度为1225。

注意,内容视图的宽度现在比父滚动视图的宽度大225。没关系,事实上,你必须这样做。请注意

... 你把后跟宽度设为负225..。

你会认为你必须“匹配”宽度 就像你平时一样。但如果你这样做,根本行不通。

您必须将前导数字和尾随数字设置为 ,永远不要设置为负数(即使宽度“更大”)

有趣的是,你实际上可以设置前/后的数字为任何 确定值(试着说“50”) ,它给你一种反弹的边际。(它通常看起来很棒: 试试吧。)任何一端的负值都将“悄无声息地中断”。

注意,令人恼火的是,通常是 Xcode (到7.3.1为止) ,

将“有益”为您设置这些值为负数!

因为它会自动帮你计数。如果是这样,它会悄悄地破裂。在第一个实例中将所有四个值设置为零。并且设置内容视图的宽度比例例中的“1000”宽得多。


编辑: 在我的大部分需求中,我最终使用了 UITableView 而不是 UIScrollView。在我看来,tableView 更加灵活和动态。

我今天在 iOS 8.4和 Xcode 6.4上也遇到了类似的问题

有一个包含滚动视图的视图,其中包含一个包含子视图的 contentView (UIView)。

到处都是自动布局。 Scrollview 边缘用约束固定在父视图边缘上。 内容视图边缘用约束固定在滚动视图边缘上。

最初,内容视图将拒绝按滚动视图的全宽调整大小。我必须在内容视图上添加一个额外的约束,使其宽度与父滚动视图匹配。或者我可以设置 contentView.centerX = = scrollView.centerX 约束。除了固定边缘之外,其中任何一个突然使内容视图的大小适当。

// Either one of these additional constraints are required to get autolayout to correctly layout the contentView. Otherwise contentView size is its minimum required size
scrollView.addConstraint(NSLayoutConstraint(item: contentView, attribute: .CenterX, relatedBy: .Equal, toItem: scrollView, attribute: .CenterX, multiplier: 1.0, constant: 0))
scrollView.addConstraint(NSLayoutConstraint(item: contentView, attribute: NSLayoutAttribute.Width, relatedBy: .Equal, toItem: scrollView, attribute: .Width, multiplier: 1.0, constant: 0.0))

使用窗体的视觉约束将内容视图的边缘固定到滚动视图,

let cvConstraints = ["H:|[contentView]|", "V:|[contentView]|"]

我使用一个例程遍历数组并将它们添加到 scrollView。

我也遇到过类似的问题。我设置了每个约束,并且一直想知道为什么它仍然可以调整一些子视图的大小。我的解决方案是将 clipsToBound 设置为 YES。

迅速中,您可以使用这个工作解决方案。

限制

ScrollView: 领导,跟随,顶部,底部 = 总览

引导,尾随,顶部,底部 = 滚动视图。高度固定/相对于内容。

您可以将宽度约束(contentView)设置为等于 scrollviews superview,但是在生成时选择 delete delete,因为您将以编程方式添加该约束。这样 IB 就不会抱怨警告了。

extension UIView {


func setupContentViewForViewWithScroll(contentView vwContent : UIView) {
//Set constraint for scrollview content
let constraint = NSLayoutConstraint(item: vwContent, attribute: NSLayoutAttribute.Width, relatedBy: .Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: self.bounds.size.width)
vwContent.addConstraint(constraint)
self.layoutSubviews()
}


}

在 View Controller viewDidLayoutSubviews中,我调用这个方法:

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.view.setupContentViewForViewWithScroll(contentView: vwContent)
}

简单自包含示例

从问题的高得票率和答案的低得票率来看,人们在这里没有找到一个可以理解和迅速解决的办法。我再加一个。这个项目是一个完全在 Interface Builder 中完成的独立示例。你应该能够在10分钟或更少的时间内完成它。然后您可以将所学到的概念应用到您自己的项目中。

enter image description here

最初的问题是关于滚动按钮的。这里我只使用 UIView,但是它们可以表示任何你喜欢的视图。我还选择了水平滚动,因为这种格式的故事板屏幕截图更紧凑。不过,垂直滚动的原理是相同的。

关键概念

  • UIScrollView应该只使用一个子视图。这是一个“ UIView”,它作为内容视图来保存你想要滚动的所有内容。
  • 使内容视图和滚动视图的 家长对于水平滚动具有相同的高度(对于垂直滚动具有相同的宽度)
  • 确保所有可滚动的内容都有一个固定的宽度,并且固定在所有的边上。

开始一个新项目

它可以只是一个单独的视图应用程序。

故事板

在这个例子中,我们将创建一个水平滚动视图。选择视图控制器,然后在大小检查器中选择 Freeform。使宽度 1,000和高度 300。这就给了我们在故事板上添加滚动内容的空间。

enter image description here

添加滚动视图

添加一个 UIScrollView并将所有四个边固定到视图控制器的根视图。

enter image description here

添加内容视图

UIView作为子视图添加到滚动视图。不要尝试向滚动视图添加大量的子视图。只要加一个 UIView。这将是您要滚动的其他视图的内容视图。将内容视图钉在滚动视图的所有四个面上。

enter image description here

平等高度

现在在文档大纲中,Command同时单击内容视图和滚动视图的 家长视图,以便同时选择它们。然后将高度设置为相等(Control从内容视图拖动到滚动视图)。因为我们是水平滚动,滚动视图的内容视图不会知道它应该有多高,除非我们这样设置它。

enter image description here

注:

  • 如果我们使内容垂直滚动,那么我们将内容视图的宽度设置为等于滚动视图的父视图的宽度。

添加内容

添加三个 UIView并给他们所有的约束。我使用8点边距为一切。

enter image description here

限制:

  • 绿色视图: 固定顶部、左边和底部边缘。宽度为400。
  • 红色视图: 固定顶部、左边和底部边缘。宽度为300。
  • 紫色视图: 钉住所有四个边缘。使宽度不管剩余的空间是(268在这种情况下)。

设置宽度约束也是键 ,以便滚动视图知道其内容视图的宽度。

结束了

仅此而已。现在可以运行项目了。它的行为应该类似于此答案顶部的滚动图像。

对于垂直滚动,只需交换此示例中的所有宽度和高度方向(测试和工作)。

进一步研究

你应该像这样组织你的布局
ViewControllerView包含 ScrollViewScrollView包含 ContainerViewContainerView包含2个 Labels
enter image description here

然后按照3个步骤 使你的 ScrollView可以滚动

  1. ScrollView引脚(上/右/下/左)设置为 ViewControllerView
    • ContainerView引脚(上/右/下/左)设置为 ScrollView
    • 设置 Horizontally in Container(不要设置 Vertically in Container)
    • Label1引脚(头儿/右/左)到 ContainerView
    • Label1引脚(右/左/底部)到 ContainerView头儿Label1

enter image description here

这里是演示项目

希望这能帮上忙