如何制作具有动态宽度的水平 UIScrollView?
How to make horizontal UIScrollView with dynamic width?
我有一个动态增加宽度的标签。
我决定将它添加到 ScrollView(以便用户可以在标签中看到任意长度的文本)。
可是我运行遇到了问题。实现这个的正确方法是什么?我要怎么做。如何制作动态宽度的水平滚动视图?
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.contentSize = .init(width: label.frame.width, height: label.frame.height)
}
func setupScrollView() {
view.addSubview(scrollView)
scrollView.addSubview(label)
let frameLayoutGuide = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
frameLayoutGuide.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 400),
frameLayoutGuide.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 100),
frameLayoutGuide.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -100),
frameLayoutGuide.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -300),
])
let contentLayoutGuide = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
contentLayoutGuide.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 400),
contentLayoutGuide.centerXAnchor.constraint(equalTo: frameLayoutGuide.centerXAnchor),
contentLayoutGuide.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -300),
label.centerXAnchor.constraint(equalTo: contentLayoutGuide.centerXAnchor),
label.centerYAnchor.constraint(equalTo: contentLayoutGuide.centerYAnchor)
])
}
抱歉,您对 scrollView.contentLayoutGuide
和 scrollView.frameLayoutGuide
的使用大错特错了。
您想将 scrollView
本身限制到它的超级视图(在本例中是主 view
)并限制滚动视图的 子视图到 scrollView 的 .contentLayoutGuide
。然后,您可以使用滚动视图的 .frameLayoutGuide
来帮助设置其子视图的大小。
因此,如果您希望标签在滚动视图中居中,然后在标签太宽无法容纳时启用水平滚动,您需要:
- 将您的标签添加到“holder”视图,限制在中心
- 将“holder”视图添加到滚动视图
- 将其位置限制在
.contentLayoutGuide
和
- 将其大小限制为
.frameLayoutGuide
- 向标签添加前导和尾随约束以强制“holder”视图在需要时增长
这是一个简单的例子。它将循环显示标签的不同长度字符串...滚动将根据标签的最终宽度自动启用或禁用(全部具有自动布局 - 无需计算大小):
class ViewController: UIViewController {
let scrollView = UIScrollView()
let label = UILabel()
let labelHolderView = UIView()
let sampleStrings: [String] = [
"Short (no scrolling).",
"A little longer (still no scrolling).",
"A much longer string, that will definitely require horizontal scrolling.",
"Just for kicks, let's make this string really, really long, to help demonstrate the benefits of using auto-layout!",
]
var stringIndex: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
setupScrollView()
}
func setupScrollView() {
// add the label to the holder view
labelHolderView.addSubview(label)
// add the holder view to the scroll view
scrollView.addSubview(labelHolderView)
// add the scroll view to the view
view.addSubview(scrollView)
// all three need this
labelHolderView.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
let safeG = view.safeAreaLayoutGuide
let contentG = scrollView.contentLayoutGuide
let frameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// let's put a 100-pt tall scroll view
// 40-pts from the bottom
// 40-pts on each side
scrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: -40.0),
scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 40.0),
scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -40.0),
scrollView.heightAnchor.constraint(equalToConstant: 100.0),
// constrain holder view to ContentGuide with Zero on all 4 sides
labelHolderView.topAnchor.constraint(equalTo: contentG.topAnchor, constant: 0.0),
labelHolderView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor, constant: 0.0),
labelHolderView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor, constant: 0.0),
labelHolderView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor, constant: 0.0),
// we only want horizontal scrolling, so constrain
// holder view height to sccroll view FrameGuide
labelHolderView.heightAnchor.constraint(equalTo: frameG.heightAnchor),
// we want the label centered horizontally
// in the holder view and in the scroll view
// so set holder view width >= FrameGuide
labelHolderView.widthAnchor.constraint(greaterThanOrEqualTo: frameG.widthAnchor, constant: 0.0),
// center the label in the holder view
label.centerXAnchor.constraint(equalTo: labelHolderView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: labelHolderView.centerYAnchor),
// as the label expands horizontally, we want at least
// 8-pts on each side
label.leadingAnchor.constraint(greaterThanOrEqualTo: labelHolderView.leadingAnchor, constant: 8.0),
label.trailingAnchor.constraint(lessThanOrEqualTo: labelHolderView.trailingAnchor, constant: -8.0),
])
// let's use some background colors so we can see the view frames
scrollView.backgroundColor = .yellow
labelHolderView.backgroundColor = .systemTeal
label.backgroundColor = .green
// add a button above the scroll view to cycle through our sample strings
let b = UIButton()
b.setTitle("Tap Me", for: [])
b.setTitleColor(.white, for: .normal)
b.setTitleColor(.lightGray, for: .highlighted)
b.backgroundColor = .systemGreen
b.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(b)
NSLayoutConstraint.activate([
b.bottomAnchor.constraint(equalTo: scrollView.topAnchor, constant: -20.0),
b.widthAnchor.constraint(equalTo: safeG.widthAnchor, multiplier: 0.6),
b.centerXAnchor.constraint(equalTo: safeG.centerXAnchor),
])
b.addTarget(self, action: #selector(gotTap(_:)), for: .touchUpInside)
// set the initial text
gotTap(nil)
}
@objc func gotTap(_ sender: Any?) -> Void {
label.text = sampleStrings[stringIndex % sampleStrings.count]
stringIndex += 1
}
}
我有一个动态增加宽度的标签。 我决定将它添加到 ScrollView(以便用户可以在标签中看到任意长度的文本)。
可是我运行遇到了问题。实现这个的正确方法是什么?我要怎么做。如何制作动态宽度的水平滚动视图?
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.contentSize = .init(width: label.frame.width, height: label.frame.height)
}
func setupScrollView() {
view.addSubview(scrollView)
scrollView.addSubview(label)
let frameLayoutGuide = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
frameLayoutGuide.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 400),
frameLayoutGuide.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 100),
frameLayoutGuide.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -100),
frameLayoutGuide.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -300),
])
let contentLayoutGuide = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
contentLayoutGuide.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 400),
contentLayoutGuide.centerXAnchor.constraint(equalTo: frameLayoutGuide.centerXAnchor),
contentLayoutGuide.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -300),
label.centerXAnchor.constraint(equalTo: contentLayoutGuide.centerXAnchor),
label.centerYAnchor.constraint(equalTo: contentLayoutGuide.centerYAnchor)
])
}
抱歉,您对 scrollView.contentLayoutGuide
和 scrollView.frameLayoutGuide
的使用大错特错了。
您想将 scrollView
本身限制到它的超级视图(在本例中是主 view
)并限制滚动视图的 子视图到 scrollView 的 .contentLayoutGuide
。然后,您可以使用滚动视图的 .frameLayoutGuide
来帮助设置其子视图的大小。
因此,如果您希望标签在滚动视图中居中,然后在标签太宽无法容纳时启用水平滚动,您需要:
- 将您的标签添加到“holder”视图,限制在中心
- 将“holder”视图添加到滚动视图
- 将其位置限制在
.contentLayoutGuide
和 - 将其大小限制为
.frameLayoutGuide
- 将其位置限制在
- 向标签添加前导和尾随约束以强制“holder”视图在需要时增长
这是一个简单的例子。它将循环显示标签的不同长度字符串...滚动将根据标签的最终宽度自动启用或禁用(全部具有自动布局 - 无需计算大小):
class ViewController: UIViewController {
let scrollView = UIScrollView()
let label = UILabel()
let labelHolderView = UIView()
let sampleStrings: [String] = [
"Short (no scrolling).",
"A little longer (still no scrolling).",
"A much longer string, that will definitely require horizontal scrolling.",
"Just for kicks, let's make this string really, really long, to help demonstrate the benefits of using auto-layout!",
]
var stringIndex: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
setupScrollView()
}
func setupScrollView() {
// add the label to the holder view
labelHolderView.addSubview(label)
// add the holder view to the scroll view
scrollView.addSubview(labelHolderView)
// add the scroll view to the view
view.addSubview(scrollView)
// all three need this
labelHolderView.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
let safeG = view.safeAreaLayoutGuide
let contentG = scrollView.contentLayoutGuide
let frameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// let's put a 100-pt tall scroll view
// 40-pts from the bottom
// 40-pts on each side
scrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: -40.0),
scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 40.0),
scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -40.0),
scrollView.heightAnchor.constraint(equalToConstant: 100.0),
// constrain holder view to ContentGuide with Zero on all 4 sides
labelHolderView.topAnchor.constraint(equalTo: contentG.topAnchor, constant: 0.0),
labelHolderView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor, constant: 0.0),
labelHolderView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor, constant: 0.0),
labelHolderView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor, constant: 0.0),
// we only want horizontal scrolling, so constrain
// holder view height to sccroll view FrameGuide
labelHolderView.heightAnchor.constraint(equalTo: frameG.heightAnchor),
// we want the label centered horizontally
// in the holder view and in the scroll view
// so set holder view width >= FrameGuide
labelHolderView.widthAnchor.constraint(greaterThanOrEqualTo: frameG.widthAnchor, constant: 0.0),
// center the label in the holder view
label.centerXAnchor.constraint(equalTo: labelHolderView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: labelHolderView.centerYAnchor),
// as the label expands horizontally, we want at least
// 8-pts on each side
label.leadingAnchor.constraint(greaterThanOrEqualTo: labelHolderView.leadingAnchor, constant: 8.0),
label.trailingAnchor.constraint(lessThanOrEqualTo: labelHolderView.trailingAnchor, constant: -8.0),
])
// let's use some background colors so we can see the view frames
scrollView.backgroundColor = .yellow
labelHolderView.backgroundColor = .systemTeal
label.backgroundColor = .green
// add a button above the scroll view to cycle through our sample strings
let b = UIButton()
b.setTitle("Tap Me", for: [])
b.setTitleColor(.white, for: .normal)
b.setTitleColor(.lightGray, for: .highlighted)
b.backgroundColor = .systemGreen
b.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(b)
NSLayoutConstraint.activate([
b.bottomAnchor.constraint(equalTo: scrollView.topAnchor, constant: -20.0),
b.widthAnchor.constraint(equalTo: safeG.widthAnchor, multiplier: 0.6),
b.centerXAnchor.constraint(equalTo: safeG.centerXAnchor),
])
b.addTarget(self, action: #selector(gotTap(_:)), for: .touchUpInside)
// set the initial text
gotTap(nil)
}
@objc func gotTap(_ sender: Any?) -> Void {
label.text = sampleStrings[stringIndex % sampleStrings.count]
stringIndex += 1
}
}