UIScrollView 使用 Autolayout 或缩放后停止滚动
UIScrollView stops scrolling with Autolayout or after zooming
我有以下代码来设置 UIScrollView 的子视图。我首先创建一个名为 MyScrollView 的 UIScrollView 的子类。然后我添加一个名为 contentView 的视图作为子视图。我将所有其他视图添加到滚动视图作为此 contentView 的子视图。问题是在使用自动布局约束设置以下代码后,scrollView 不会滚动。
public class MyScrollView:UIScrollView {
private var contentView:UIView!
override init(frame: CGRect) {
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
self.isScrollEnabled = true
self.isDirectionalLockEnabled = true
self.showsHorizontalScrollIndicator = true
self.showsVerticalScrollIndicator = false
self.decelerationRate = .normal
self.delaysContentTouches = false
self.bouncesZoom = true
setupSubviews()
}
private func setupSubviews() {
contentView = UIView()
contentView.backgroundColor = UIColor.clear
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.isUserInteractionEnabled = true
self.addSubview(contentView)
contentView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: CGFloat(19), constant: CGFloat(5)).isActive = true
contentView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 2.0, constant: CGFloat(5)).isActive = true
// self.contentSize = CGSize(width: 10000, height: 2*frame.height)
let subviewWidth = CGFloat(450)
let subviewHeight = CGFloat(20)
//Add other subviews to contentView
(0...4).compactMap { i in
(0...19).compactMap { j in
let x = CGFloat(j)*(subviewWidth + 5.0) + 5.0
let y = CGFloat(i)*(subviewHeight + 5.0) + 5.0
let subview = UIView(frame: CGRect(x: x, y: y, width: subviewWidth, height: subviewHeight))
contentView.addSubview(subview)
}
}
问题是,当自动布局约束设置如上时,滚动视图不会滚动。如果没有设置约束,它会滚动,但在缩放后,滚动会再次被禁用。我只是在滚动视图委托中设置了这个。
public func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return scrollView.subviews.first //contentView
}
对于你的方法...
您想将 contentView
限制在滚动视图的内容布局指南中。这将自动确定“可滚动”区域。
由于您没有对 contentView 的子视图使用 auto-layout,因此每次添加新子视图时都需要更新 .contentView
宽度和高度限制。
举个例子。我们将在距顶部/前导/尾部 40 点处创建一个 MyScrollView
,高度为 240 点。
子视图将有一个绿色背景,contentView 将有一个蓝色背景,滚动视图将有一个红色背景(所以我们可以很容易地看到框架)。
我们将从一个子视图开始,以便于查看会发生什么。一开始只有一个小的subview,不会有滚动。
每次我们点击任何地方,我们都会向 contentView 添加一个新的子视图,背景为黄色,并根据需要更新 contentView 的宽度和高度约束。您会看到蓝色内容视图变大以匹配子视图。一旦子视图导致内容视图大于滚动视图的宽度或高度,滚动将自动进行。
public class MyScrollView: UIScrollView {
private var contentView:UIView!
// contentView's Width and Height constraints
// we'll update the .constant values when we add subviews
private var cvWidthConstraint: NSLayoutConstraint!
private var cvHeightConstraint: NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
self.translatesAutoresizingMaskIntoConstraints = false
self.isScrollEnabled = true
self.isDirectionalLockEnabled = true
self.showsHorizontalScrollIndicator = true
self.showsVerticalScrollIndicator = false
self.decelerationRate = .normal
self.delaysContentTouches = false
self.bouncesZoom = true
setupSubviews()
}
private func setupSubviews() {
contentView = UIView()
contentView.backgroundColor = UIColor.clear
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.isUserInteractionEnabled = true
self.addSubview(contentView)
// constrain contentView to scroll view's Content Layout Guide
// this determines the "scrollable" area
contentView.leadingAnchor.constraint(equalTo: self.contentLayoutGuide.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: self.contentLayoutGuide.trailingAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: self.contentLayoutGuide.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: self.contentLayoutGuide.bottomAnchor).isActive = true
// create contentView's Width and Height constraints
cvWidthConstraint = contentView.widthAnchor.constraint(equalToConstant: 0.0)
cvHeightConstraint = contentView.heightAnchor.constraint(equalToConstant: 0.0)
// activate them
cvWidthConstraint.isActive = true
cvHeightConstraint.isActive = true
//Add other subviews to contentView
// we'll start with ONE subview, so we can easily see what's happening
let subviewWidth = CGFloat(240)
let subviewHeight = CGFloat(20)
let subview = UILabel(frame: CGRect(x: 5, y: 5, width: subviewWidth, height: subviewHeight))
subview.textAlignment = .center
subview.text = "First"
subview.backgroundColor = .green
contentView.addSubview(subview)
// so we can see the frames
self.backgroundColor = .red
self.contentView.backgroundColor = .blue
// update the contentView constraints
updateContent()
}
private func updateContent() -> Void {
// array of subviews
let views = contentView.subviews
// get the
// max Y of the subview frames
// max X of the subview frames
guard let maxYValue = views.lazy.map({ [=10=].frame.maxY }).max(),
let maxXValue = views.lazy.map({ [=10=].frame.maxX }).max()
else { return }
// update contentView Width and Height constraints
cvWidthConstraint.constant = maxXValue + 5.0
cvHeightConstraint.constant = maxYValue + 5.0
}
func addLabel(frame _frame: CGRect, text: String) -> Void {
// add a new subview
let subview = UILabel(frame: _frame)
subview.textAlignment = .center
subview.text = text
subview.backgroundColor = .yellow
contentView.addSubview(subview)
// update the contentView constraints
updateContent()
}
}
class ExampleViewController: UIViewController {
let myScrollView = MyScrollView()
var count: Int = 1
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(myScrollView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain custom scroll view Top / Leading / Trailing
// 40-pts from the safe-area edges
myScrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
myScrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
myScrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
// scroll view Height: 240-pts
myScrollView.heightAnchor.constraint(equalToConstant: 240.0),
])
// add tap gesture recognizer so we can add a new subview
// every time we tap
let t = UITapGestureRecognizer(target: self, action: #selector(self.testAddSubview))
view.addGestureRecognizer(t)
}
@objc func testAddSubview() -> Void {
let s = "New Subview \(count)"
let x: CGFloat = CGFloat(count) * 60.0
let y: CGFloat = CGFloat(count) * 35.0
myScrollView.addLabel(frame: CGRect(x: x, y: y, width: 200, height: 30), text: s)
count += 1
}
}
启动时 - 一个子视图 - 无滚动:
添加一个新的子视图后 - 蓝色内容视图更大,但不足以滚动:
添加 4 个新子视图后 - 现在我们可以滚动了:
我有以下代码来设置 UIScrollView 的子视图。我首先创建一个名为 MyScrollView 的 UIScrollView 的子类。然后我添加一个名为 contentView 的视图作为子视图。我将所有其他视图添加到滚动视图作为此 contentView 的子视图。问题是在使用自动布局约束设置以下代码后,scrollView 不会滚动。
public class MyScrollView:UIScrollView {
private var contentView:UIView!
override init(frame: CGRect) {
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
self.isScrollEnabled = true
self.isDirectionalLockEnabled = true
self.showsHorizontalScrollIndicator = true
self.showsVerticalScrollIndicator = false
self.decelerationRate = .normal
self.delaysContentTouches = false
self.bouncesZoom = true
setupSubviews()
}
private func setupSubviews() {
contentView = UIView()
contentView.backgroundColor = UIColor.clear
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.isUserInteractionEnabled = true
self.addSubview(contentView)
contentView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: CGFloat(19), constant: CGFloat(5)).isActive = true
contentView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 2.0, constant: CGFloat(5)).isActive = true
// self.contentSize = CGSize(width: 10000, height: 2*frame.height)
let subviewWidth = CGFloat(450)
let subviewHeight = CGFloat(20)
//Add other subviews to contentView
(0...4).compactMap { i in
(0...19).compactMap { j in
let x = CGFloat(j)*(subviewWidth + 5.0) + 5.0
let y = CGFloat(i)*(subviewHeight + 5.0) + 5.0
let subview = UIView(frame: CGRect(x: x, y: y, width: subviewWidth, height: subviewHeight))
contentView.addSubview(subview)
}
}
问题是,当自动布局约束设置如上时,滚动视图不会滚动。如果没有设置约束,它会滚动,但在缩放后,滚动会再次被禁用。我只是在滚动视图委托中设置了这个。
public func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return scrollView.subviews.first //contentView
}
对于你的方法...
您想将 contentView
限制在滚动视图的内容布局指南中。这将自动确定“可滚动”区域。
由于您没有对 contentView 的子视图使用 auto-layout,因此每次添加新子视图时都需要更新 .contentView
宽度和高度限制。
举个例子。我们将在距顶部/前导/尾部 40 点处创建一个 MyScrollView
,高度为 240 点。
子视图将有一个绿色背景,contentView 将有一个蓝色背景,滚动视图将有一个红色背景(所以我们可以很容易地看到框架)。
我们将从一个子视图开始,以便于查看会发生什么。一开始只有一个小的subview,不会有滚动。
每次我们点击任何地方,我们都会向 contentView 添加一个新的子视图,背景为黄色,并根据需要更新 contentView 的宽度和高度约束。您会看到蓝色内容视图变大以匹配子视图。一旦子视图导致内容视图大于滚动视图的宽度或高度,滚动将自动进行。
public class MyScrollView: UIScrollView {
private var contentView:UIView!
// contentView's Width and Height constraints
// we'll update the .constant values when we add subviews
private var cvWidthConstraint: NSLayoutConstraint!
private var cvHeightConstraint: NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
self.translatesAutoresizingMaskIntoConstraints = false
self.isScrollEnabled = true
self.isDirectionalLockEnabled = true
self.showsHorizontalScrollIndicator = true
self.showsVerticalScrollIndicator = false
self.decelerationRate = .normal
self.delaysContentTouches = false
self.bouncesZoom = true
setupSubviews()
}
private func setupSubviews() {
contentView = UIView()
contentView.backgroundColor = UIColor.clear
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.isUserInteractionEnabled = true
self.addSubview(contentView)
// constrain contentView to scroll view's Content Layout Guide
// this determines the "scrollable" area
contentView.leadingAnchor.constraint(equalTo: self.contentLayoutGuide.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: self.contentLayoutGuide.trailingAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: self.contentLayoutGuide.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: self.contentLayoutGuide.bottomAnchor).isActive = true
// create contentView's Width and Height constraints
cvWidthConstraint = contentView.widthAnchor.constraint(equalToConstant: 0.0)
cvHeightConstraint = contentView.heightAnchor.constraint(equalToConstant: 0.0)
// activate them
cvWidthConstraint.isActive = true
cvHeightConstraint.isActive = true
//Add other subviews to contentView
// we'll start with ONE subview, so we can easily see what's happening
let subviewWidth = CGFloat(240)
let subviewHeight = CGFloat(20)
let subview = UILabel(frame: CGRect(x: 5, y: 5, width: subviewWidth, height: subviewHeight))
subview.textAlignment = .center
subview.text = "First"
subview.backgroundColor = .green
contentView.addSubview(subview)
// so we can see the frames
self.backgroundColor = .red
self.contentView.backgroundColor = .blue
// update the contentView constraints
updateContent()
}
private func updateContent() -> Void {
// array of subviews
let views = contentView.subviews
// get the
// max Y of the subview frames
// max X of the subview frames
guard let maxYValue = views.lazy.map({ [=10=].frame.maxY }).max(),
let maxXValue = views.lazy.map({ [=10=].frame.maxX }).max()
else { return }
// update contentView Width and Height constraints
cvWidthConstraint.constant = maxXValue + 5.0
cvHeightConstraint.constant = maxYValue + 5.0
}
func addLabel(frame _frame: CGRect, text: String) -> Void {
// add a new subview
let subview = UILabel(frame: _frame)
subview.textAlignment = .center
subview.text = text
subview.backgroundColor = .yellow
contentView.addSubview(subview)
// update the contentView constraints
updateContent()
}
}
class ExampleViewController: UIViewController {
let myScrollView = MyScrollView()
var count: Int = 1
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(myScrollView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain custom scroll view Top / Leading / Trailing
// 40-pts from the safe-area edges
myScrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
myScrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
myScrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
// scroll view Height: 240-pts
myScrollView.heightAnchor.constraint(equalToConstant: 240.0),
])
// add tap gesture recognizer so we can add a new subview
// every time we tap
let t = UITapGestureRecognizer(target: self, action: #selector(self.testAddSubview))
view.addGestureRecognizer(t)
}
@objc func testAddSubview() -> Void {
let s = "New Subview \(count)"
let x: CGFloat = CGFloat(count) * 60.0
let y: CGFloat = CGFloat(count) * 35.0
myScrollView.addLabel(frame: CGRect(x: x, y: y, width: 200, height: 30), text: s)
count += 1
}
}
启动时 - 一个子视图 - 无滚动:
添加一个新的子视图后 - 蓝色内容视图更大,但不足以滚动:
添加 4 个新子视图后 - 现在我们可以滚动了: