//
// ScrollWorkViewController.swift
//
// Created by DonMag on 6/12/19.
//
import UIKit
class ScrollWorkViewController: UIViewController {
let theScrollView: UIScrollView = {
let v = UIScrollView()
v.backgroundColor = .red
return v
}()
let contentView: UIView = {
let v = UIView()
v.backgroundColor = UIColor(red: 0.25, green: 0.25, blue: 1.0, alpha: 1.0)
return v
}()
let stackView: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.alignment = .fill
v.distribution = .fill
v.spacing = 20
return v
}()
let topLabel: UILabel = {
let v = UILabel()
v.font = UIFont.boldSystemFont(ofSize: 32.0)
v.backgroundColor = .yellow
return v
}()
let centerLabel: UILabel = {
let v = UILabel()
v.font = UIFont.systemFont(ofSize: 17.0)
v.numberOfLines = 0
v.backgroundColor = .green
return v
}()
let bottomLabel: UILabel = {
let v = UILabel()
v.font = UIFont.systemFont(ofSize: 14.0)
v.numberOfLines = 0
v.backgroundColor = .cyan
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
[theScrollView, contentView, stackView, topLabel, centerLabel, bottomLabel].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
view.addSubview(theScrollView)
theScrollView.addSubview(contentView)
contentView.addSubview(stackView)
stackView.addArrangedSubview(topLabel)
stackView.addArrangedSubview(centerLabel)
stackView.addArrangedSubview(bottomLabel)
let contentViewHeightConstraint = contentView.heightAnchor.constraint(equalTo: theScrollView.heightAnchor, constant: 0.0)
contentViewHeightConstraint.priority = .defaultLow
NSLayoutConstraint.activate([
// constrain all 4 sides of the scroll view to the safe area
theScrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0.0),
theScrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0.0),
theScrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0.0),
theScrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0.0),
// constrain all 4 sides of the content view to the scroll view
contentView.topAnchor.constraint(equalTo: theScrollView.topAnchor, constant: 0.0),
contentView.bottomAnchor.constraint(equalTo: theScrollView.bottomAnchor, constant: 0.0),
contentView.leadingAnchor.constraint(equalTo: theScrollView.leadingAnchor, constant: 0.0),
contentView.trailingAnchor.constraint(equalTo: theScrollView.trailingAnchor, constant: 0.0),
// constrain width of content view to width of scroll view
contentView.widthAnchor.constraint(equalTo: theScrollView.widthAnchor, constant: 0.0),
// constrain the stack view >= 8-pts from the top
// <= minus 8-pts from the bottom
// 40-pts leading and trailing
stackView.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: 8.0),
stackView.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -8.0),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 40.0),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -40.0),
// constrain stack view centerY to contentView centerY
stackView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0.0),
// activate the contentView's height constraint
contentViewHeightConstraint,
])
topLabel.text = "Anger"
bottomLabel.text = "Based on information from Wikipedia APA Dictionary of Psychology"
// a sample paragraph of text
let centerSampleText = "Anger is an intense emotion defined as a response to a perceived provocation, the invasion of one’s boundaries, or a threat. From an evolutionary standpoint, anger servers to mobilise psychological resources in order to address the threat/invasion. Anger is directed at an individual of equal status."
// change to repeat the center-label sample text
let numberOfParagraphs = 2
var s = ""
for i in 1...numberOfParagraphs {
s += "\(i). " + centerSampleText
if i < numberOfParagraphs {
s += "\n\n"
}
}
centerLabel.text = s
}
}
class ScrollWorkViewController: UIViewController {
let theScrollView: UIScrollView = {
let v = UIScrollView()
v.backgroundColor = .systemYellow
return v
}()
let contentView: UIView = {
let v = UIView()
v.backgroundColor = .systemBlue
return v
}()
let stackView: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.alignment = .fill
v.distribution = .fill
v.spacing = 20
return v
}()
let topLabel: UILabel = {
let v = UILabel()
v.font = UIFont.boldSystemFont(ofSize: 32.0)
v.backgroundColor = .yellow
return v
}()
let centerLabel: UILabel = {
let v = UILabel()
v.font = UIFont.systemFont(ofSize: 17.0)
v.numberOfLines = 0
v.backgroundColor = .green
return v
}()
let bottomLabel: UILabel = {
let v = UILabel()
v.font = UIFont.systemFont(ofSize: 14.0)
v.numberOfLines = 0
v.backgroundColor = .cyan
return v
}()
// a sample paragraph of text
let centerSampleText = "Anger is an intense emotion defined as a response to a perceived provocation, the invasion of one’s boundaries, or a threat. From an evolutionary standpoint, anger servers to mobilise psychological resources in order to address the threat/invasion. Anger is directed at an individual of equal status."
// update the center-label text when numberOfParagraphs changes
var numberOfParagraphs = 1 {
didSet {
var s = ""
for i in 1...numberOfParagraphs {
s += "\(i). " + centerSampleText
if i < numberOfParagraphs {
s += "\n\n"
}
}
centerLabel.text = s
}
}
override func viewDidLoad() {
super.viewDidLoad()
let btnA = UIButton()
btnA.setTitle("Add", for: [])
btnA.setTitleColor(.white, for: .normal)
btnA.setTitleColor(.lightGray, for: .highlighted)
btnA.backgroundColor = .systemGreen
let btnB = UIButton()
btnB.setTitle("Remove", for: [])
btnB.setTitleColor(.white, for: .normal)
btnB.setTitleColor(.lightGray, for: .highlighted)
btnB.backgroundColor = .systemRed
[btnA, btnB, theScrollView, contentView, stackView, topLabel, centerLabel, bottomLabel].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
view.addSubview(btnA)
view.addSubview(btnB)
view.addSubview(theScrollView)
theScrollView.addSubview(contentView)
contentView.addSubview(stackView)
stackView.addArrangedSubview(topLabel)
stackView.addArrangedSubview(centerLabel)
stackView.addArrangedSubview(bottomLabel)
let g = view.safeAreaLayoutGuide
let cg = theScrollView.contentLayoutGuide
let fg = theScrollView.frameLayoutGuide
// constrain height of content view to height of scroll view's Frame Layout Guide
// with less-than-required Priority so it can get taller when the content gets taller
let contentViewHeightConstraint = contentView.heightAnchor.constraint(equalTo: fg.heightAnchor, constant: 0.0)
contentViewHeightConstraint.priority = .defaultLow
NSLayoutConstraint.activate([
// constrain buttons at top
btnA.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
btnA.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
btnB.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
btnB.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
btnB.leadingAnchor.constraint(equalTo: btnA.trailingAnchor, constant: 20.0),
btnB.widthAnchor.constraint(equalTo: btnA.widthAnchor),
// constrain scroll view Top to buttons Bottom plus 8-points "spacing"
// leading/trailing/bottom to the safe area
theScrollView.topAnchor.constraint(equalTo: btnA.bottomAnchor, constant: 8.0),
theScrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
theScrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
theScrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
// constrain all 4 sides of the content view to the scroll view's Content Layout Guide
contentView.topAnchor.constraint(equalTo: cg.topAnchor, constant: 0.0),
contentView.leadingAnchor.constraint(equalTo: cg.leadingAnchor, constant: 0.0),
contentView.trailingAnchor.constraint(equalTo: cg.trailingAnchor, constant: 0.0),
contentView.bottomAnchor.constraint(equalTo: cg.bottomAnchor, constant: 0.0),
// constrain width of content view to width of scroll view's Frame Layout Guide
contentView.widthAnchor.constraint(equalTo: fg.widthAnchor, constant: 0.0),
// constrain the stack view >= 8-pts from the top
// <= minus 8-pts from the bottom
// 40-pts leading and trailing
stackView.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: 8.0),
stackView.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -8.0),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 40.0),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -40.0),
// constrain stack view centerY to contentView centerY
stackView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0.0),
// activate the contentView's height constraint
contentViewHeightConstraint,
])
topLabel.text = "Anger"
bottomLabel.text = "Based on information from Wikipedia APA Dictionary of Psychology"
numberOfParagraphs = 1
btnA.addTarget(self, action: #selector(addTapped(_:)), for: .touchUpInside)
btnB.addTarget(self, action: #selector(removeTapped(_:)), for: .touchUpInside)
}
@objc func addTapped(_ sender: Any?) {
numberOfParagraphs += 1
}
@objc func removeTapped(_ sender: Any?) {
if numberOfParagraphs > 1 {
numberOfParagraphs -= 1
}
}
}
4条答案
按热度按时间cfh9epnr1#
您可以通过将标签嵌入到堆栈视图中并将堆栈视图嵌入到UIView中来完成此操作。标签文本将垂直展开堆栈视图,堆栈视图将垂直展开内容视图,内容视图将控制滚动视图的
.contentSize
。黑色为滚动视图;蓝色是内容视图;堆栈视图仅显示为细灰色轮廓;标签为黄色、绿色和青色。背景颜色只是让它更容易看到什么是什么。
步骤很多,但要明确:
0
约束以滚动视图Vertical / Fill / Fill / Spacing: 20
>= 8
将contentView的高度Priority设置为250将允许它根据标签中的文本垂直扩展。
将top和bottom stackView约束设置为>= 8将“推动”contentView的顶部和底部,但当您没有足够的文本超出垂直边界时,允许额外的空间。
结果如下:
这里有一个故事板,所有的东西都可以参考:
这里有一个快速的例子,只通过代码复制布局/功能:
编辑-由于这个答案仍然偶尔会得到“支持票”,我已经更新了代码,以反映滚动视图
.contentLayoutGuide
和.frameFlayoutGuide
的更现代的用法。还添加了按钮,以交互方式添加/删除文本演示居中。对于一两个段落,内容不够高,无法滚动,但仍保持垂直居中:
对于3个或更多段落(在iPhone SE上),我们现在可以滚动:
wfveoks02#
为了补充DonMag的答案,您实际上可以通过仅使用
UIScrollView
和UIStackView
来完成完全相同的事情。这适用于iOS 11及更高版本,因为它使用UIScrollView
上的contentLayoutGuide
和frameLayoutGuide
属性。当我提到滚动视图的内容视图时,它只是指滚动视图中的可滚动内容布局区域。它指的是内容布局指南,而不是堆栈视图。
步骤如下,用伪代码编写(只需添加等效的约束):
scrollView.contentLayoutGuide.height >= scrollView.frameLayoutGuide.height
-这将滚动视图内容设置为至少与滚动视图本身一样高。通过这样做,我们还不能完全集中内容。stackView.centerY == scrollView.contentLayoutGuide.centerY
-这将强制堆栈视图与滚动视图内容视图垂直居中。但是等等,如果堆栈视图太短怎么办?请记住,在第1步中,我们强制内容大小至少与滚动视图本身一样高。这意味着,如果堆栈视图不够高,不足以引起滚动,它实际上将在内容视图中居中,内容视图与滚动视图一样高,从而引起期望的效果。stackView.top/bottom <= (inside) scrollView.contentLayoutGuide.top/bottom
-这只是设置堆栈视图的边缘,以便顶部和底部位于滚动视图的内容视图内。1.(可选)
stackView.top/bottom == (inside) scrollView.contentLayoutGuide.top/bottom
withdefaultLow
priority -这将强制滚动视图的内容视图具有固有高度,以防视图调试器出现问题。这对于垂直约束应该是足够的。添加必要的水平约束,一切都应该很好!
4zcjmb1e3#
我也遇到过同样的问题,当标签没有填满scrollView(否则顶部对齐)时,我试图在scrollView中垂直居中标签,并找到了一个简单的解决方案,因为它是脏的,所有在IB中没有扩展或子类。
首先,在处理UIScrollView时设置通常的约束:
1.限制到scrollView内容布局指南的顶部、底部、前导、尾随子视图
1.子视图宽度等于scrollView宽度
然后使子视图的高度大于或等于scrollView的高度(这会触发一个约束错误)。现在,将此约束乘数设置为接近1但不完全为1的任何值(例如:0.999999)。约束错误消失,子视图的行为符合预期。
这一点并不值得骄傲,但它可以保存一些时间,人们在匆忙。
pxiryf3j4#
巨大的 prop 给DonMag的伟大解释。然而,由于某种原因,当我实现它时,我的ScrollView不会滚动。下面是对我有用的,在Xcode 13的Interface Builder中,所以你可以看到ScrollView的
Content Layout Guide
与Frame Layout Guide
约束。步骤:
Scroll View
拖到视图控制器中。引脚领先/顶部/尾随/底部的Safe Area
或任何。这将确定Scroll View
的帧,从而确定Frame Layout Guide
。View
拖动到Scroll View
中。将leading/top/trailing/bottom固定到Scroll View
的Content Layout Guide
(滚动视图的“内部”)。这些约束允许这个新View
的大小来确定滚动视图的可滚动区域:如果View
大于滚动视图的框架,则滚动视图将允许滚动,否则不允许。现在我们只需要设置View
的大小;它立场无关紧要。(如果Scroll View
允许滚动,则默认情况下将从顶部开始“向上滚动”。View
中。因此,View
的 * 最小尺寸 * 应该是总的可见区域,也就是Scroll View
的框架。假设我们只想允许垂直滚动,将View
的宽度设置为Scroll View
的Frame Layout Guide
宽度。另外,将View
的高度设置为 * 大于或等于 *Scroll View
的Frame Layout Guide
高度,因为我们当然希望启用垂直滚动 * 如果 *View
中的内容大于滚动视图的框架。Stack View
(或您想要的任何视图/内容)拖到View
中。将Stack View
的centerY
设置为其superview的centerY
。View
的高度。我们之前设定了一个最小高度,那么最大高度呢?这可以通过另一个约束来满足:将Stack View
的高度设置为 * 小于或等于 *Views
的高度。这将神奇地确保如果Stack View
的高度增长超过View
的最小高度,View
的高度将相应地增加,使该区域可滚动。(请注意,还有其他方法可以设置View
的高度,例如使用View
的子视图的顶部和底部引用,如其他答案所示。)Stack View
的水平约束。不幸的是,界面生成器抱怨“滚动视图需要约束:Y位置或高度”这可能是一个Xcode / auto布局错误?我在控制台中没有看到任何错误。