无法检测到另一个 UIScrollView 内的 UIScrollView 中的点击
Unable to detect tap in UIScrollView inside another UIScrollView
我有一个横跨整个 ViewController
的垂直滚动 UIScrollView
,其中包含这个 ContentView
(UIView
),所以我能够检测到外部的触摸事件滚动视图范围:
import UIKit
class ContentView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let reversedSubviews = subviews.reversed()
let hitSubview = reversedSubviews
.first(where: { [=10=].hitTest(convert(point, to: [=10=]), with: event) != nil })
if hitSubview != nil {
return hitSubview
}
return super.hitTest(point, with: event)
}
}
这是视图层次结构
UIScrollView (vertical scroll) //Works
|
-- ContentView(UIView)
|
--UIScrollView (horizontal scroll) //Works
|
-- UIView
|
--UIButton //This does not work
|
--UIScrollView (horizontal scroll) //Works
|
-- UIView
|
--UIButton //This does not work
在这个ContentView
里面我还有其他几个水平滚动条UIScrollViews
。但是由于 ContentView
.
,我无法检测到这些滚动视图中的触摸事件
我应该如何处理这个问题才能检测到所有滚动视图中的触摸事件,即使子视图超出范围也是如此?
将 ContentView
放在水平滚动视图中没有帮助..
当我对 UITableView
和 UICollectionView
以及如何避免在相同方向滚动的两个滚动视图的嵌套(水平和水平或垂直)了解不多时,我也遇到了这个问题& 垂直)。
当您的内部 UI 元素绘制在任何屏幕视图的可用视口(在屏幕上呈现的框架)之外时,通常会发生此问题。有时它是由于约束设置问题而发生的。
如何调试?
要快速调试,请确定没有响应的按钮的父级 UIView
。从 Identity Inspector
检查 Clips to Bounds
默认情况下是 false
。 Clips to Bounds
正确,您只能看到可以与之交互的视图。
您可以在下面的 YouTube 视频中查看问题:
解决方案:
- 重新访问父视图的约束或框架计算。
UICollectionView
(水平滚动)在另一个 UICollectionView
或 UITableView
(垂直滚动)内,如果您不遵循该方法可能是个好方法。
这是一个非常基本的例子。
遵循您的层次结构:
Main View
|
-- UIScrollView (vertical scroll) - Red
|
-- ContentView(UIView) - Medium Blue
|
-- UIScrollView (horizontal scroll) - Green
|
-- UIView - Light Gray
|
-- UIButton - Blue
|
-- UIView - Light Gray
|
-- UIButton - Blue
|
-- UIScrollView (horizontal scroll) - Green
|
-- UIView - Light Gray
|
-- UIButton - Blue
|
-- UIView - Light Gray
|
-- UIButton - Blue
|
这是它的外观 - 可以点击按钮:
并且,在向下滚动并向右滚动每个水平滚动视图后:
示例代码...不需要 @IBOutlet
或 @IBAction
连接,只需将视图控制器的 class 分配给 MultiScrollViewController
:
class MultiScrollViewController: UIViewController {
let vertScrollView = UIScrollView()
let contentView = UIView()
let hScrollA = UIScrollView()
let hScrollB = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
[vertScrollView, contentView, hScrollA, hScrollB].forEach {
[=11=].translatesAutoresizingMaskIntoConstraints = false
}
contentView.addSubview(hScrollA)
contentView.addSubview(hScrollB)
vertScrollView.addSubview(contentView)
view.addSubview(vertScrollView)
let g = view.safeAreaLayoutGuide
let svContentG = vertScrollView.contentLayoutGuide
let svFrameG = vertScrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// vertical scroll view with 20-pts on each side for "padding"
vertScrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
vertScrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
vertScrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
vertScrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
// content view Top / Leading / Bottom 12-pts to content layout guide
contentView.topAnchor.constraint(equalTo: svContentG.topAnchor, constant: 12.0),
contentView.leadingAnchor.constraint(equalTo: svContentG.leadingAnchor, constant: 12.0),
contentView.bottomAnchor.constraint(equalTo: svContentG.bottomAnchor, constant: -12.0),
// content view Trailing to content layout guide
contentView.trailingAnchor.constraint(equalTo: svContentG.trailingAnchor, constant: 0.0),
// content view Width: scroll frame width minus 24-pts (we don't want horizontal scrolling)
contentView.widthAnchor.constraint(equalTo: svFrameG.widthAnchor, constant: -24.0),
// horizontal scroll view A Top / Leading / Trailing 8-pts to contentView
hScrollA.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8.0),
hScrollA.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0),
hScrollA.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0),
// horizontal scroll view B Top 20-pts from Bottom of hScrollA
hScrollB.topAnchor.constraint(equalTo: hScrollA.bottomAnchor, constant: 20.0),
// horizontal scroll view B Leading / Trailing / Bottom 8-pts to contentView
hScrollB.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0),
hScrollB.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0),
hScrollB.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8.0),
// so we have some vertical scrolling, make each horizontal scroll view
// 60% the height of the vertical scroll view frame
hScrollA.heightAnchor.constraint(equalTo: svFrameG.heightAnchor, multiplier: 0.6),
hScrollB.heightAnchor.constraint(equalTo: hScrollA.heightAnchor),
])
// each horizontal scroll view will have
// two UIViews, each with a UIButton
let btn1inA = UIButton()
let btn2inA = UIButton()
let btn1inB = UIButton()
let btn2inB = UIButton()
let btn1inAView = UIView()
let btn2inAView = UIView()
let btn1inBView = UIView()
let btn2inBView = UIView()
let btnViews = [btn1inAView, btn2inAView, btn1inBView, btn2inBView]
let btns = [btn1inA, btn2inA, btn1inB, btn2inB]
let ttls = ["Button 1 in A", "Button 2 in A", "Button 1 in B", "Button 2 in B"]
for (btn, str) in zip(btns, ttls) {
btn.setTitle(str, for: [])
btn.setTitleColor(.white, for: .normal)
btn.setTitleColor(.gray, for: .highlighted)
btn.titleLabel?.font = .boldSystemFont(ofSize: 18)
btn.backgroundColor = .blue
btn.contentEdgeInsets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16)
btn.translatesAutoresizingMaskIntoConstraints = false
btn.addTarget(self, action: #selector(self.btnTap(_:)), for: .touchUpInside)
}
for (btn, v) in zip(btns, btnViews) {
v.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
v.translatesAutoresizingMaskIntoConstraints = false
v.addSubview(btn)
// center each button in a view with 8-pts padding on all 4 sides
NSLayoutConstraint.activate([
btn.topAnchor.constraint(equalTo: v.topAnchor, constant: 8.0),
btn.leadingAnchor.constraint(equalTo: v.leadingAnchor, constant: 8.0),
btn.trailingAnchor.constraint(equalTo: v.trailingAnchor, constant: -8.0),
btn.bottomAnchor.constraint(equalTo: v.bottomAnchor, constant: -8.0),
])
}
// add button-holding-views to horizontal scroll views
hScrollA.addSubview(btn1inAView)
hScrollA.addSubview(btn2inAView)
hScrollB.addSubview(btn1inBView)
hScrollB.addSubview(btn2inBView)
let svAContentG = hScrollA.contentLayoutGuide
let svAFrameG = hScrollA.frameLayoutGuide
let svBContentG = hScrollB.contentLayoutGuide
let svBFrameG = hScrollB.frameLayoutGuide
NSLayoutConstraint.activate([
// button 1 in hScroll A
// top == content top + 20
btn1inAView.topAnchor.constraint(equalTo: svAContentG.topAnchor, constant: 20.0),
// leading == content leading + 20
btn1inAView.leadingAnchor.constraint(equalTo: svAContentG.leadingAnchor, constant: 20.0),
// button 2 in hScroll A
// leading == button 1 leading + 600 (so we get horizontal scrolling
btn2inAView.leadingAnchor.constraint(equalTo: btn1inAView.trailingAnchor, constant: 600.0),
// trailing == content trailing - 20
btn2inAView.trailingAnchor.constraint(equalTo: svAContentG.trailingAnchor, constant: -20.0),
// bottom to content bottom
btn2inAView.bottomAnchor.constraint(equalTo: svAContentG.bottomAnchor),
// bottom 20-pts from hScroll A frame bottom (so it's at lower-right)
btn2inAView.bottomAnchor.constraint(equalTo: svAFrameG.bottomAnchor, constant: -20.0),
// button 1 in hScroll B
// top == content top + 20
btn1inBView.topAnchor.constraint(equalTo: svBContentG.topAnchor, constant: 20.0),
// leading == content leading + 20
btn1inBView.leadingAnchor.constraint(equalTo: svBContentG.leadingAnchor, constant: 20.0),
// button 2 in hScroll A
// leading == button 1 leading + 600 (so we get horizontal scrolling
btn2inBView.leadingAnchor.constraint(equalTo: btn1inBView.trailingAnchor, constant: 600.0),
// trailing == content trailing - 20
btn2inBView.trailingAnchor.constraint(equalTo: svBContentG.trailingAnchor, constant: -20.0),
// bottom to content bottom
btn2inBView.bottomAnchor.constraint(equalTo: svBContentG.bottomAnchor),
// bottom 20-pts from hScroll B frame bottom (so it's at lower-right)
btn2inBView.bottomAnchor.constraint(equalTo: svBFrameG.bottomAnchor, constant: -20.0),
])
// some background colors so we can see the frames
view.backgroundColor = .yellow
vertScrollView.backgroundColor = .red
// medium blue
contentView.backgroundColor = UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1.0)
hScrollA.backgroundColor = .green
hScrollB.backgroundColor = .green
}
@objc func btnTap(_ sender: UIButton) -> Void {
print(sender.currentTitle ?? "No Button Title")
}
}
我有一个横跨整个 ViewController
的垂直滚动 UIScrollView
,其中包含这个 ContentView
(UIView
),所以我能够检测到外部的触摸事件滚动视图范围:
import UIKit
class ContentView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let reversedSubviews = subviews.reversed()
let hitSubview = reversedSubviews
.first(where: { [=10=].hitTest(convert(point, to: [=10=]), with: event) != nil })
if hitSubview != nil {
return hitSubview
}
return super.hitTest(point, with: event)
}
}
这是视图层次结构
UIScrollView (vertical scroll) //Works
|
-- ContentView(UIView)
|
--UIScrollView (horizontal scroll) //Works
|
-- UIView
|
--UIButton //This does not work
|
--UIScrollView (horizontal scroll) //Works
|
-- UIView
|
--UIButton //This does not work
在这个ContentView
里面我还有其他几个水平滚动条UIScrollViews
。但是由于 ContentView
.
我应该如何处理这个问题才能检测到所有滚动视图中的触摸事件,即使子视图超出范围也是如此?
将 ContentView
放在水平滚动视图中没有帮助..
当我对 UITableView
和 UICollectionView
以及如何避免在相同方向滚动的两个滚动视图的嵌套(水平和水平或垂直)了解不多时,我也遇到了这个问题& 垂直)。
当您的内部 UI 元素绘制在任何屏幕视图的可用视口(在屏幕上呈现的框架)之外时,通常会发生此问题。有时它是由于约束设置问题而发生的。
如何调试?
要快速调试,请确定没有响应的按钮的父级 UIView
。从 Identity Inspector
检查 Clips to Bounds
默认情况下是 false
。 Clips to Bounds
正确,您只能看到可以与之交互的视图。
您可以在下面的 YouTube 视频中查看问题:
解决方案:
- 重新访问父视图的约束或框架计算。
UICollectionView
(水平滚动)在另一个UICollectionView
或UITableView
(垂直滚动)内,如果您不遵循该方法可能是个好方法。
这是一个非常基本的例子。
遵循您的层次结构:
Main View
|
-- UIScrollView (vertical scroll) - Red
|
-- ContentView(UIView) - Medium Blue
|
-- UIScrollView (horizontal scroll) - Green
|
-- UIView - Light Gray
|
-- UIButton - Blue
|
-- UIView - Light Gray
|
-- UIButton - Blue
|
-- UIScrollView (horizontal scroll) - Green
|
-- UIView - Light Gray
|
-- UIButton - Blue
|
-- UIView - Light Gray
|
-- UIButton - Blue
|
这是它的外观 - 可以点击按钮:
并且,在向下滚动并向右滚动每个水平滚动视图后:
示例代码...不需要 @IBOutlet
或 @IBAction
连接,只需将视图控制器的 class 分配给 MultiScrollViewController
:
class MultiScrollViewController: UIViewController {
let vertScrollView = UIScrollView()
let contentView = UIView()
let hScrollA = UIScrollView()
let hScrollB = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
[vertScrollView, contentView, hScrollA, hScrollB].forEach {
[=11=].translatesAutoresizingMaskIntoConstraints = false
}
contentView.addSubview(hScrollA)
contentView.addSubview(hScrollB)
vertScrollView.addSubview(contentView)
view.addSubview(vertScrollView)
let g = view.safeAreaLayoutGuide
let svContentG = vertScrollView.contentLayoutGuide
let svFrameG = vertScrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// vertical scroll view with 20-pts on each side for "padding"
vertScrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
vertScrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
vertScrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
vertScrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
// content view Top / Leading / Bottom 12-pts to content layout guide
contentView.topAnchor.constraint(equalTo: svContentG.topAnchor, constant: 12.0),
contentView.leadingAnchor.constraint(equalTo: svContentG.leadingAnchor, constant: 12.0),
contentView.bottomAnchor.constraint(equalTo: svContentG.bottomAnchor, constant: -12.0),
// content view Trailing to content layout guide
contentView.trailingAnchor.constraint(equalTo: svContentG.trailingAnchor, constant: 0.0),
// content view Width: scroll frame width minus 24-pts (we don't want horizontal scrolling)
contentView.widthAnchor.constraint(equalTo: svFrameG.widthAnchor, constant: -24.0),
// horizontal scroll view A Top / Leading / Trailing 8-pts to contentView
hScrollA.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8.0),
hScrollA.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0),
hScrollA.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0),
// horizontal scroll view B Top 20-pts from Bottom of hScrollA
hScrollB.topAnchor.constraint(equalTo: hScrollA.bottomAnchor, constant: 20.0),
// horizontal scroll view B Leading / Trailing / Bottom 8-pts to contentView
hScrollB.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0),
hScrollB.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0),
hScrollB.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8.0),
// so we have some vertical scrolling, make each horizontal scroll view
// 60% the height of the vertical scroll view frame
hScrollA.heightAnchor.constraint(equalTo: svFrameG.heightAnchor, multiplier: 0.6),
hScrollB.heightAnchor.constraint(equalTo: hScrollA.heightAnchor),
])
// each horizontal scroll view will have
// two UIViews, each with a UIButton
let btn1inA = UIButton()
let btn2inA = UIButton()
let btn1inB = UIButton()
let btn2inB = UIButton()
let btn1inAView = UIView()
let btn2inAView = UIView()
let btn1inBView = UIView()
let btn2inBView = UIView()
let btnViews = [btn1inAView, btn2inAView, btn1inBView, btn2inBView]
let btns = [btn1inA, btn2inA, btn1inB, btn2inB]
let ttls = ["Button 1 in A", "Button 2 in A", "Button 1 in B", "Button 2 in B"]
for (btn, str) in zip(btns, ttls) {
btn.setTitle(str, for: [])
btn.setTitleColor(.white, for: .normal)
btn.setTitleColor(.gray, for: .highlighted)
btn.titleLabel?.font = .boldSystemFont(ofSize: 18)
btn.backgroundColor = .blue
btn.contentEdgeInsets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16)
btn.translatesAutoresizingMaskIntoConstraints = false
btn.addTarget(self, action: #selector(self.btnTap(_:)), for: .touchUpInside)
}
for (btn, v) in zip(btns, btnViews) {
v.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
v.translatesAutoresizingMaskIntoConstraints = false
v.addSubview(btn)
// center each button in a view with 8-pts padding on all 4 sides
NSLayoutConstraint.activate([
btn.topAnchor.constraint(equalTo: v.topAnchor, constant: 8.0),
btn.leadingAnchor.constraint(equalTo: v.leadingAnchor, constant: 8.0),
btn.trailingAnchor.constraint(equalTo: v.trailingAnchor, constant: -8.0),
btn.bottomAnchor.constraint(equalTo: v.bottomAnchor, constant: -8.0),
])
}
// add button-holding-views to horizontal scroll views
hScrollA.addSubview(btn1inAView)
hScrollA.addSubview(btn2inAView)
hScrollB.addSubview(btn1inBView)
hScrollB.addSubview(btn2inBView)
let svAContentG = hScrollA.contentLayoutGuide
let svAFrameG = hScrollA.frameLayoutGuide
let svBContentG = hScrollB.contentLayoutGuide
let svBFrameG = hScrollB.frameLayoutGuide
NSLayoutConstraint.activate([
// button 1 in hScroll A
// top == content top + 20
btn1inAView.topAnchor.constraint(equalTo: svAContentG.topAnchor, constant: 20.0),
// leading == content leading + 20
btn1inAView.leadingAnchor.constraint(equalTo: svAContentG.leadingAnchor, constant: 20.0),
// button 2 in hScroll A
// leading == button 1 leading + 600 (so we get horizontal scrolling
btn2inAView.leadingAnchor.constraint(equalTo: btn1inAView.trailingAnchor, constant: 600.0),
// trailing == content trailing - 20
btn2inAView.trailingAnchor.constraint(equalTo: svAContentG.trailingAnchor, constant: -20.0),
// bottom to content bottom
btn2inAView.bottomAnchor.constraint(equalTo: svAContentG.bottomAnchor),
// bottom 20-pts from hScroll A frame bottom (so it's at lower-right)
btn2inAView.bottomAnchor.constraint(equalTo: svAFrameG.bottomAnchor, constant: -20.0),
// button 1 in hScroll B
// top == content top + 20
btn1inBView.topAnchor.constraint(equalTo: svBContentG.topAnchor, constant: 20.0),
// leading == content leading + 20
btn1inBView.leadingAnchor.constraint(equalTo: svBContentG.leadingAnchor, constant: 20.0),
// button 2 in hScroll A
// leading == button 1 leading + 600 (so we get horizontal scrolling
btn2inBView.leadingAnchor.constraint(equalTo: btn1inBView.trailingAnchor, constant: 600.0),
// trailing == content trailing - 20
btn2inBView.trailingAnchor.constraint(equalTo: svBContentG.trailingAnchor, constant: -20.0),
// bottom to content bottom
btn2inBView.bottomAnchor.constraint(equalTo: svBContentG.bottomAnchor),
// bottom 20-pts from hScroll B frame bottom (so it's at lower-right)
btn2inBView.bottomAnchor.constraint(equalTo: svBFrameG.bottomAnchor, constant: -20.0),
])
// some background colors so we can see the frames
view.backgroundColor = .yellow
vertScrollView.backgroundColor = .red
// medium blue
contentView.backgroundColor = UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1.0)
hScrollA.backgroundColor = .green
hScrollB.backgroundColor = .green
}
@objc func btnTap(_ sender: UIButton) -> Void {
print(sender.currentTitle ?? "No Button Title")
}
}