This is the View layer I created. I want to transfer all Touch Events that happen in UIView to UIScrollView, but I don't know how. I didn't put it as a SubView of ScrollView because UIView should be lock. I want to use the default Gesture in UIScrollView for up or down drag (PanGesture) that happens in UIView. I made a PanGesture in the ViewController using ResponderChain to respond, but when it is Touch State.Ended like ScrollView I couldn't implement a scroll that ends smoothly. Any way to solve this problem? Thank you for your reply.
One approach that may work for you...
Use a UIView
subclass for your "stationary pink view" and implement hitTest
:
class MyParentView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let hitView = super.hitTest(point, with: event)
if hitView == self {
return nil
} else {
return hitView
}
}
}
that will allow the touch/drag to effectively "pass through" to the scroll view underneath, while still allowing user interaction with its own subviews.
Here's an example controller showing it in use:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red: 0.61, green: 0.86, blue: 0.93, alpha: 1.0)
let scrollView = UIScrollView()
scrollView.backgroundColor = UIColor(red: 0.96, green: 0.93, blue: 0.79, alpha: 1.0)
let pinkView = MyParentView()
pinkView.backgroundColor = UIColor(red: 0.96, green: 0.70, blue: 0.86, alpha: 1.0)
// a button and label to add to the pink view
let btn = UIButton()
btn.setTitle("Some Button", for: [])
btn.setTitleColor(.white, for: .normal)
btn.setTitleColor(.lightGray, for: .highlighted)
btn.backgroundColor = .systemRed
btn.layer.cornerRadius = 8
btn.translatesAutoresizingMaskIntoConstraints = false
let label = UILabel()
label.text = "Some Label"
[btn, label].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
pinkView.addSubview(v)
}
// a vertical stack view with a bunch of labels
// to use as the scroll content
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 40.0
for i in 1...30 {
let l = UILabel()
l.text = "Label \(i)"
stack.addArrangedSubview(l)
}
// add stack view to scroll view
stack.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(stack)
// add scroll view to view
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
// add pink view ot view
pinkView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(pinkView)
let g = view.safeAreaLayoutGuide
let cg = scrollView.contentLayoutGuide
let fg = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// constrain scroll view to all 4 sides with 20-points "padding"
scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
// constrain stack view to all 4 sides of scroll view's Content Layout Guide
// with 20-points "padding"
stack.topAnchor.constraint(equalTo: cg.topAnchor, constant: 20.0),
stack.leadingAnchor.constraint(equalTo: cg.leadingAnchor, constant: 20.0),
stack.trailingAnchor.constraint(equalTo: cg.trailingAnchor, constant: -20.0),
stack.bottomAnchor.constraint(equalTo: cg.bottomAnchor, constant: -20.0),
// stack view width is scroll view's Content Layout Guide width
// minus 40-points (20-points on each side)
stack.widthAnchor.constraint(equalTo: fg.widthAnchor, constant: -40.0),
// constrain pink view relative to scroll view
// 10-points from top
// 100-points leading (so we can see the scrolling)
// trailing
// height of 320-points
pinkView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 10.0),
pinkView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 100.0),
pinkView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: 0.0),
pinkView.heightAnchor.constraint(equalToConstant: 320.0),
// constrain button and label inside pink view
btn.topAnchor.constraint(equalTo: pinkView.topAnchor, constant: 40.0),
btn.leadingAnchor.constraint(equalTo: pinkView.leadingAnchor, constant: 20.0),
btn.trailingAnchor.constraint(equalTo: pinkView.trailingAnchor, constant: -20.0),
label.topAnchor.constraint(equalTo: btn.bottomAnchor, constant: 20.0),
label.leadingAnchor.constraint(equalTo: pinkView.leadingAnchor, constant: 20.0),
label.trailingAnchor.constraint(equalTo: pinkView.trailingAnchor, constant: -20.0),
])
// add an action for the button so we know it can be tapped
btn.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
// a little "styling"
scrollView.layer.cornerRadius = 16
pinkView.layer.cornerRadius = 16
}
@objc func btnTapped(_ sender: Any?) {
print("Tapped")
}
}
The result - I inset the "pink view" from the leading edge of the scroll view to make it easy to see the scroll contents scrolling behind it:
Touching and dragging on the yellow OR the pink will scroll the scrollView as normal.