Context: In the iOS UIKit project, written in swift
lets say I have a Super view - 'A' with frame (0, 0, 320, 200)
and I have a subview say 'B' with frame (0, 0, 320, 600)
and the view 'B' is added as a subview to the view 'A'
now the subview 'B' is having a table view with in it
Issue facing
tablew view with in my subview B is responsive only till its super view position i.e. its scrollable and didSelectRowAt is invoked only within the frame (0, 0, 320, 200) which its super view frame
when I tap on any row beyond that frame, is not responding, could somebody please help me how do I fix this wired issue, thanks in advance.
CodePudding user response:
Probably the best way to handle this is by overriding a hitTest
method to return rather your target view. Something like this:
class OverridingHitTestView: UIView {
var forwardTo: UIView?
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let originalResult = super.hitTest(point, with: event)
if let forwardTo, originalResult == self {
return forwardTo.hitTest(point, with: event)
} else {
return originalResult
}
}
}
This OverridingHitTestView
will send events to whatever you set forwardTo
view. In your case it should be the table view which is your sub view.
Your OverridingHitTestView
must be placed below your table view and it needs to be large enough to include all of the area you wish to handle.
I created a trivial example all-in-code just to test run it.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let container = OverridingHitTestView(frame: view.bounds)
view.addSubview(container)
let smallView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 400.0, height: 300.0))
smallView.backgroundColor = UIColor.green
container.addSubview(smallView)
let largeView = UIStackView(frame: CGRect(x: 0.0, y: 0.0, width: 400.0, height: 600.0))
largeView.axis = .vertical
largeView.distribution = .fillEqually
(0..<10).forEach { index in
let button = UIButton(frame: CGRect(x: 0.0, y: 0.0, width: 400.0, height: 60.0))
button.setTitle("Button \(index 1)", for: .normal)
button.addTarget(self, action: #selector(onButtonPressed), for: .touchUpInside)
button.setTitleColor(.black, for: .normal)
largeView.addArrangedSubview(button)
}
container.forwardTo = largeView
smallView.addSubview(largeView)
}
@objc private func onButtonPressed(_ sender: UIButton) {
print("Pressed button \(sender.titleLabel?.text ?? "[NA]")")
}
}
class OverridingHitTestView: UIView {
var forwardTo: UIView?
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let originalResult = super.hitTest(point, with: event)
if let forwardTo, originalResult == self {
return forwardTo.hitTest(point, with: event)
} else {
return originalResult
}
}
}
To dig a bit more into the hit test:
let originalResult = super.hitTest(point, with: event)
if let forwardTo, originalResult == self {
return forwardTo.hitTest(point, with: event)
} else {
return originalResult
}
The idea is that we check what would the view originally report. And if it would report self
we forward the call to our target view.
The reason for it is that self
will be returned in cases where user did actually press within this view. And also that it did not press some subview on this view. This allows that other elements such as buttons still work correctly within this view (not all events are forwarded to the target view).
I hope this sets you on the right path if not solve your issue. You could still use the same method but change conditions to for instance look for touch locations within a window or whatever really. The point being that you simply need to return a correct view on hitTest
.