我花了两天时间尝试了 Mixed and Pure Autolayout approaches 的各种解决方案,以实现在自动布局之前微不足道的滚动视图设置,现在它是官方的 - 我一定太愚蠢了。我主要在 Storyboard 中进行设置(嗯,就是这样)。
所以这是我的求助请求。
视图树:
UIView
-UIView
-UIView
..-UIScrollview
...-UIButton
...-UIButton
...-UIButton
按钮应该水平滚动(从左到右,反之亦然)。有人可以让我知道如何设置约束以使用纯自动布局实现这一目标吗???
--
我尝试了混合方法,如下所示:
UIView
- UIView
- UIView
..-UIScrollview
...-UIView (contentview)
....-UIButton
....-UIButton
....-UIButton
...并根据 Apple 的技术说明为 contentview
和 translatesAutoresizingMaskIntoConstraints
设置设置固定的宽度和高度限制。按钮和滚动视图是使用约束设置的。这让滚动视图滚动(耶),但是,它滚动得太远了!据我所知,滚动宽度比我设置的内容视图翻了一番???!!!???
我也尝试了纯自动布局方法,无论是否使用 contentview
。 所有视图均为 translatesAutoresizingMaskIntoConstraints=NO
,但 self.view
除外。按钮具有固定的宽度/高度约束,并固定到滚动视图的所有四个边缘。没有滚动。
所以我完全困惑为什么我不能让它正常工作。非常感谢任何帮助,如果您需要任何其他信息,请询问!
带有解决方案的更新屏幕截图 - buttonZ 约束:
https://i.stack.imgur.com/fdkm8.png
编辑@ Jamie Forrest 所以解决方案原来是最后一个按钮上的错误尾随约束。我设置的值不是 6441,而是负数,-6441。棘手的是,在情节提要中设置值时,Pin 工具栏中有两个选项:
https://i.stack.imgur.com/18dfp.png
当前画布值是负数(导致不滚动),下面的选项是正数(激活滚动)。这意味着我并不愚蠢,但我猜至少是半盲的。虽然,为了我的辩护,XCode 没有显示“不正确”设置的错误,这不是有点令人不安吗?
再次编辑现在这很有趣......将尾随值从 -6441(无滚动)更改为 6441 启用滚动。但是我的老朋友“太多内容大小”又回来了,导致内容大小是应有大小的两倍!获得正确内容滚动的解决方案是将尾随约束设置为零!这在 Storyboard 中工作时并不明显,但在查看 @Infinity James 的代码时,它应该是这样的。
UIScrollView
的 contentSize
。 contentSize
定义了您可以滚动多少。所以你最终必须在某个地方手动设置它。对于其余部分,您可以使用布局约束来设置彼此相对的视图框架。
当您将它们粘贴到此处时,很难看到您的约束的确切值和设置,所以我不确定从您的屏幕截图中哪里出错。
我创建了一个基本的 sample project,而不是解释您的设置有什么问题,它的视图层次结构和约束设置与您描述的非常相似。示例项目中的水平滚动按预期工作,该项目使用 Apple 在 Technical Note 中描述的“Pure AutoLayout”方法。
最初让 Auto Layout 与 UIScrollView
一起使用时,我也遇到了很多麻烦。让它工作的关键是确保滚动视图中的所有项目加在一起,具有最终链接到滚动视图所有方面的约束,并有助于 AutoLayout 系统能够确定 contentSize 为滚动视图将大于其框架。看起来你试图在你的代码中这样做,但也许你在那里有一些多余的约束使得 contentSize 太小了。
另外值得注意的是,正如其他人提到的,使用 AutoLayout 和 UIScrollview,您不再显式设置 contentSize。 AutoLayout System 根据您的约束计算 contentSize。
我还发现 this ebook chapter 非常有助于让我了解这一切是如何工作的。希望这一切都有帮助。
大声笑欢迎来到愚蠢俱乐部。我是创始人之一。 :D
对于垂直滚动:我可以让它工作的唯一方法(iOS 8、Xcode 6 和纯自动布局)是在我的滚动视图中添加以下约束(都与超级视图相关):
等宽
等高
中心 Y 对齐
中心 X 对齐
我的结构:
UIView
- ScrollView
- Subview
- Subview
- Subview
- Subview
- ...
这是最终结果:
https://web.archive.org/web/20201110021703/https://s3.amazonaws.com/f.cl.ly/items/3a3G1Y3P0a2u301f091t/Vertical%20UIScrollView%20with%20pure%20AutoLayout.gif
这是设置:
https://s3.amazonaws.com/f.cl.ly/items/2H1X1c030B3Z3g2B3E2W/Vertical%20UIScrollView%20with%20pure%20AutoLayout.png
这是项目。
希望这可以避免有人在凌晨 5 点睡觉。 :D
简单的独立示例
从对问题的高票数和对答案的低票数来看,人们在这里找不到可理解且快速的解决方案。让我尝试添加一个。这个项目是一个完全在 Interface Builder 中完成的独立示例。您应该能够在 10 分钟或更短的时间内完成它。然后,您可以将学到的概念应用到您自己的项目中。
https://i.stack.imgur.com/L4puB.gif
最初的问题询问滚动按钮。这里我只使用 UIView
,但它们可以代表您喜欢的任何视图。我还选择了水平滚动,因为这种格式的情节提要屏幕截图更紧凑。不过,垂直滚动的原理是相同的。
关键概念
UIScrollView 应该只使用一个子视图。这是一个“UIView”,用作内容视图来保存您希望滚动的所有内容。
使内容视图和滚动视图的父级具有相同的水平滚动高度。 (垂直滚动的等宽)
确保所有可滚动内容都具有设定的宽度并固定在所有侧面。
开始一个新项目
它可以只是一个单一的视图应用程序。
故事板
在这个例子中,我们将制作一个水平滚动视图。选择 View Controller,然后在 Size Inspector 中选择 Freeform。使宽度 1,000
和高度 300
。这只是为我们在情节提要上提供了空间来添加将滚动的内容。
https://i.stack.imgur.com/sWjKD.png
添加滚动视图
添加一个 UIScrollView
并将所有四个边固定到视图控制器的根视图。
https://i.stack.imgur.com/o8WKv.png
添加内容视图
将 UIView
作为子视图添加到滚动视图。 这是关键。 不要尝试在滚动视图中添加大量子视图。只需添加一个 UIView
。这将是您要滚动的其他视图的内容视图。将内容视图固定到所有四个侧面的滚动视图。
https://i.stack.imgur.com/h70fZ.png
等高
现在在 Document Outline 中,Command 单击内容视图和滚动视图的父视图以同时选择它们。然后将高度设置为相等(控制从内容视图拖动到滚动视图)。这也是关键。因为我们是水平滚动的,所以除非我们这样设置,否则滚动视图的内容视图不会知道它应该有多高。
https://i.stack.imgur.com/ZXsnk.png
笔记:
如果我们让内容垂直滚动,那么我们会将内容视图的宽度设置为等于滚动视图的父视图的宽度。
添加内容
添加三个 UIView
并赋予它们所有约束。我对所有内容都使用了 8 点边距。
https://i.stack.imgur.com/HsPVr.png
约束:
绿色视图:固定顶部、左侧和底部边缘。使宽度为 400。
红色视图:固定顶部、左侧和底部边缘。使宽度为 300。
紫色视图:固定所有四个边缘。无论剩余空间是多少(在本例中为 268),都设置宽度。
设置宽度约束也是关键,以便滚动视图知道其内容视图的宽度。
完成的
就这样。您现在可以运行您的项目。它的行为应该类似于此答案顶部的滚动图像。
对于垂直滚动,只需交换此示例中的所有宽度和高度方向(已测试并正常工作)。
进一步研究
iOS:如何使自动布局在 ScrollView 上工作
如何在 Interface Builder 中使用自动布局配置 UIScrollView
YouTube 视频教程:UIScrollView - 如何将您的视图保持在屏幕上
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 设置为填充 containerView 的大小(因此 containerView 必须具有一定的大小)。
然后,您可以通过将 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 使用 AutoLayout 有很多问题,我们忽略的关键点是 UIScrollView 的内部视图对 Content View 进行约束,而不是 UIScrollView 本身。参考Technical Note TN2154,可以找到:
UIScrollView 类通过更改其边界的原点来滚动其内容。为了使用自动布局,滚动视图中的顶部、左侧、底部和右侧边缘现在意味着其内容视图的边缘。
https://i.stack.imgur.com/E3ueh.png
你可以发现尾随空间是 500 点,如果对 UIScrollView 进行约束,视图将被错过放置,应该更新它的框架。但是,没有警告也没有错误。因为所有的约束都是针对内容视图的。
UIScrollView 会根据内部视图的约束来计算内容视图的大小。 (例如,内容大小:width = 100(前导空格)+ 200(视图宽度)+ 500(尾随空格),height = 131(顶部间距)+ 200(高度)+ 269(底部间距)
如何在 UIScrollView 中为视图添加约束:
对内容视图中视图的位置进行成像。为内容视图的边缘添加上、右、下、左间距,此外,还添加这些视图的宽度和高度。
这一切都完成了。
使用滚动视图处理 AutoLayout 的一种简单方法是在滚动视图中添加一个包含所有子视图的容器视图。
结论:使用 UIScrollView 理解 AutoLayout 的关键是内部视图对内容视图而不是 UIScrollView 本身进行约束。
以下解决方案适用于具有自动布局且没有 contentSize 的 scrollView:
将滚动视图拖放到 viewController 并应用任何约束来覆盖您想要的空间。将 UIView 拖放到 scrollView 内,使其覆盖 scrollView 的整个空间,并将约束应用于 scrollView 的顶部、左侧、右侧、底部空间。根据滚动的需要设置内部视图的高度(如果需要水平滚动,则设置宽度)。如果需要,这部分也可以通过代码完成。批判的。在点 (3) 中将高度设置为某个较大的值后,返回点 (2) 并确保将顶部、左侧、右侧、底部的值设置回零,因为 Xcode 可能会在您强制时为您更改它们改变(3)中的高度。
你完成了。现在,您可以在此视图上添加任意数量的控件并应用彼此相关的约束(如果没有此视图,这些约束似乎不起作用)。如果您不想使用此视图,则必须为与 scrollView 相关的每个控件(彼此不相关)应用约束。
压倒性的提示.......
批判的。让我们为清楚起见说 UIScrollView 是 1000 宽和 100 高。 (事实上,通常这些值是动态的,当然,取决于设备的宽度等。但现在只说 1000 宽和 100 高。)假设你正在做一个水平滚动。所以在 UIScrollView 里面放一个 UIView。 (即“内容视图”。)将内容视图顶部、底部、前导、尾随的所有四个约束设置为滚动视图。将它们全部归零,即使这看起来是错误的。将内容 UIView 的高度设置为 100 并忘记这一点。现在:你想水平滚动,所以将内容视图的宽度设置为 1225。
请注意,内容视图的宽度现在比父滚动视图的宽度大 225。没关系:事实上,你必须这样做。注意
...您没有将尾随宽度设置为负 225...
你会认为你必须像往常一样“匹配”宽度。但如果你这样做,它根本不起作用。
您必须将前导和尾随数字设置为零,绝不是负数(即使宽度“更大”)
有趣的是,您实际上可以将前导/尾随数字设置为任何正值(尝试说“50”),它会为您提供某种反弹余量。 (它通常看起来很棒:试试看。)任何一端的任何负值都会“悄无声息地破坏”。
请注意,令人发指的是,通常是 Xcode(无论如何从 7.3.1 开始),
将“有用地”为您将这些值设置为负数!
因为它会尝试自动为您计算它们。如果是这样,它将无声地破裂。在第一个实例中将所有四个值都设置为零。并将内容视图的宽度设置为比示例中的“1000”宽得多。
编辑:我最终使用 UITableView 而不是 UIScrollView 来满足我的大部分要求。因为 tableView 在我看来更加灵活和动态。
我假设您在使用 contentSize
时遇到了问题。查看 this blog post,了解在使用“纯”自动布局方法时如何处理 contentSize
。它的要点是您的约束隐含地定义了内容大小。使用 AutoLayout 时,您从不明确设置它。我在博文末尾附上了示例项目来演示它是如何工作的
您可能已经看过技术说明中的一段。您可以使用固定到滚动视图边缘的约束来隐式设置滚动视图的内容大小。
这是一个简单的例子。创建一个具有一个视图的故事板,该视图具有一个滚动视图。设置滚动视图约束以使其适合您放入的视图的大小。
在该滚动视图内添加一个视图。使用约束显式设置该视图的大小(并确保该大小大于滚动视图)。
现在向该内部视图添加四个约束,将内部视图的四个边缘锁定到其父滚动视图。这四个约束将导致内容大小扩大以适应内部视图。
如果您有多个要添加到滚动视图的视图,例如水平布局,则将第一个子视图的左侧锁定到滚动视图的左侧,将子视图水平锁定,然后将右侧最后一个子视图的一侧到滚动视图的右侧。这些约束将强制滚动视图的内容大小扩展以适应所有子视图及其约束。
如果您的问题是“我如何将一堆 UITextFields 放在垂直滚动的 UIScrollView 中,以便它们在获得焦点时移出键盘”,最好的答案是:
不。
改为使用带有静态单元格的 UITableViewController。
如果您的视图控制器显示在 UINavigationController 中,则您可以免费获得这种滚动方式的行为,并且所有内容插入 Just Work。
https://i.stack.imgur.com/H7wJx.png
然后按照 3 个步骤让您的 ScrollView
可以滚动
将 ScrollView 引脚(上/右/下/左)设置为 ViewControllerView 将 ContainerView 引脚(上/右/下/左)设置为 ScrollView 在容器中水平设置(不要在容器中垂直设置) Label1 引脚(上/右/左)到 ContainerView Label1 引脚(右/左/底部)到 ContainerView 和顶部到 Label1
https://i.stack.imgur.com/t9CoG.gif
这是演示项目
希望这有帮助
纯自动布局方法效果很好,但如果您从非自动布局迁移,设置起来会很痛苦。我已经做过几次了,我有一些一般性的提示:
从小处着手:即使这意味着重新创建故事板视图,也可以从几个元素开始并慢慢构建视图,确保在添加一些元素后测试滚动是否有效。
关闭所有内容的 translatesAutoresizingMaskIntoConstraints:这一直是我约束冲突的原因。
正确设置你的 UIScrollView 约束:确保滚动视图在所有方面都连接到父视图,否则它根本不会展开。
经过一段时间处理这个问题,我终于找到了解决方案。我正在使用通用班级大小的故事板(600x600)。我创建了一个滚动视图大小的 UIView (contentView),并为滚动视图的顶部、底部、前导和尾随创建了约束。然后我手动将 contentView 的大小裁剪为 600x600。故事板停止尝试调整所有内容,我可以工作,但在真实设备或模拟器上的视图看起来很糟糕。我制作了 2 个这种剪裁尺寸的约束插座。
@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;
效果很好。
我花了几天的时间试图找到一个解决方案,如何使用 AutoLayout 视图和嵌入式 Scrollview,使滚动视图在可见屏幕中居中,适用于所有设备/屏幕尺寸以及屏幕旋转。
我花了几天时间尝试仅使用 Autolayout 来做到这一点,并且接近但从未足够接近。所以最后我不得不在 viewDidLoad 中为每个屏幕添加 3 行代码。
请参阅下面的解决方案:
创建滚动视图并用您想要的任何对象填充它打开自动布局然后将滚动视图垂直和水平居中选择视图,然后选择“添加缺少的约束” - 这就是它的事情结果是生成了很多约束。为视图创建了 2 个新视图:“Horiz space scrollview to View”和“Vert space scrollview to view”,反之亦然。删除“Horiz space scrollview to View”,这样您现在在视图上剩下 3 个约束。用于在视图中输入滚动视图的 2 和用于在滚动视图和视图之间设置垂直空间的 2 现在通过单击和 Ctrl 将 Vert 约束链接到您的代码,将其拖动到头文件并创建一个 NSLayoutConstraint IBOutlet(我叫我的constraintVertVtoSV) 现在转到 .m 文件并将这些代码行添加到 viewDidLoad 中(使用填充量来获得正确的垂直居中) if (IPAD) { self.constraintVertVtoSV.constant = 150.0;这现在应该在所有设备上运行,并且正确居中并且仍然可以正确滚动。
如果像我一样,您只需在子视图内使用没有约束的静态内容,就像您可以这样做:
override func viewDidLayoutSubviews() {
scrollView.contentSize = CGSizeMake(320, 800)
}
我今天在 iOS 8.4、Xcode 6.4 上遇到的类似问题
有一个包含滚动视图的视图,包含一个包含子视图的内容视图(UIView)。
一切都是自动布局。滚动视图边缘被固定到具有约束的父视图边缘。内容视图边缘被固定到带有约束的滚动视图边缘。
最初,内容视图会拒绝调整为滚动视图的整个宽度。我必须在内容视图上添加一个额外的约束,使其宽度与父滚动视图相匹配。或者我可以设置一个 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]|"]
我使用例程遍历数组并将它们添加到滚动视图。
我遇到了类似的问题。我设置了每一个约束并且总是想知道为什么它仍然调整一些子视图的大小。我的解决方案是将 clipsToBounds 设置为 YES。
您可以迅速使用此工作解决方案。
约束
ScrollView
:前导、尾随、顶部、底部 = Superview
ContentView
:前导、尾随、顶部、底部 = ScrollView。高度固定/相对于内容。
您可以将宽度约束(contentView)设置为等于滚动视图超级视图,但在构建时选择删除删除,因为您将以编程方式添加该约束。这只是为了让 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)
}
我知道这是一个外行的解决方案,而不是 Apple 在文档中建议的解决方案,但它对我有用两次,具有不同的内容并且可以非常快速地设置:在情节提要视图控制器中插入 UIView。在 UIView 中插入一个 Table View、Dynamic、0 Prototype 单元格、Style Plain 或 Grouped。在 Table View 中插入一个 Scroll View,在 Scroll View 中插入内容。就是这样,自定义视图控制器中没有设置。