There have been similar questions before and I have referred to Capturing touches on a subview outside the frame of its superview using hitTest:withEvent: and Delivering touch events to a view outside the bounds of its parent view. But they do not seem to answer my particular problem.
I have a custom control with the following structure:
+-------------------------------+ | UIButton | +-------------------------------+ | | | | | UITableView | | | | | +------------------------- +
The custom control overrides the UIButton
subclass and adds the UITableView
as its subView. The idea is to have the whole control act like a dropdown. When the UIButton
is pressed, the UITableView
will dropdown and enable selection of a choice.
This all works fine with the hitTest
overridden in UIButton
as described in the Apple's Q&A link above, if the control is an immediate child of the topmost UIView
. However, if the control is in another view hierarchy, the UITableView
is not receiving touch events.
The following is the hitTest code used:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { // Convert the point to the target view's coordinate system. // The target view isn't necessarily the immediate subview let pointForTargetView = self.spinnerTableView.convertPoint(point, fromView:self) if (CGRectContainsPoint(self.spinnerTableView.bounds, pointForTargetView)) { // The target view may have its view hierarchy, // so call its hitTest method to return the right hit-test view return self.spinnerTableView.hitTest(pointForTargetView, withEvent:event); } return super.hitTest(point, withEvent: event) }
Edit:
I apologise for not having been able to look at the answers and check if they resolve the issue, due to some other tasks that require immediate attention. I shall check them and accept one that helps. Thanks for your patience.
5 Answers
Answers 1
As you stated:
However, if the control is in another view hierarchy, the UITableView is not receiving touch events.
Even if you made it working what if you have subviewed this control under another UIView??
This way we have a burden of converting points for the view hierarchy, my suggestion is that, as you can see in this control, it adds the tableview on the same hierarchy where the control itself is present. (e.g., it adds the table view on the controller where this control was added)
A part from the link:
-(void)textFieldDidBeginEditing:(UITextField *)textField { [self setupSuggestionList]; [suggestionListView setHidden:NO]; // Add list to the super view. if(self.dataSourceDelegate && [self.dataSourceDelegate isKindOfClass:UIViewController.class]) { [((UIViewController *)self.dataSourceDelegate).view addSubview:suggestionListView]; } // Setup list as per the given direction [self adjustListFrameForDirection:dropDownDirection]; }
Hope that helps!
Answers 2
I was thinking about the UIResponder
, something like:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { // Convert the point to the target view's coordinate system. // The target view isn't necessarily the immediate subview var responder: UIResponder = self while responder.nextResponder() != nil { responder = responder.nextResponder()! if responder is UITableView { // Got UITableView break; } } let pointForTargetView = responder.convertPoint(point, fromView:self) if (CGRectContainsPoint(responder.bounds, pointForTargetView)) { // The target view may have its view hierarchy, // so call its hitTest method to return the right hit-test view return self.responder.hitTest(pointForTargetView, withEvent:event); } return super.hitTest(point, withEvent: event) }
If this method don't work, try to convertPoint
with superview:
When you are sure that self.spinnerTableView
is not nil and it is your table, do:
let pointForTargetView = self.spinnerTableView.superView.convertPoint(point, fromView:self)
Answers 3
I think you should overridden pointInside
implement:
class Control: UIButton { var dropdown: Bool = false override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool { if dropdown { return CGRectContainsPoint(CGRect(x: 0, y:0, width: self.bounds.width, self.bounds.height + spinnerTableView.frame.height), point) } return super.pointInside(point, withEvent: event) } }
like this, superview call control's hittest
when touch outside control bounds, it won't return nil.
But there are still a problem, if the control is in another view hierarchy, the UITableView may not receiving touch events.
I think this problem is normal,you can't decide other view's pointInside or not which under control, if the control superview hitTest
return nil, the control hitTest
method will not be called. Unless you overridden all view pointInside
method which under the control, but it is unrealistic.
Answers 4
You need to subClass UIView,and use it as your UIViewController's root view.
class HitTestBottomView: UIView { override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { if let hitTestView = self.getHitTestButtonFrom(self){ let pointForHitTestView = hitTestView.convertPoint(point, fromView: self) let pointForHitTestTableView = hitTestView.spinnerTableView.convertPoint(point, fromView: self) if CGRectContainsPoint(hitTestView.bounds, pointForHitTestView) || CGRectContainsPoint(hitTestView.spinnerTableView.bounds, pointForHitTestTableView){ return hitTestView.hitTest(pointForHitTestView, withEvent: event) } } return super.hitTest(point, withEvent: event) } func getHitTestButtonFrom(view:UIView) ->HitTestButton?{ if let testView = view as? HitTestButton { return testView } for subView in view.subviews { if let testView = self.getHitTestButtonFrom(subView) { return testView } } return nil } }
Answers 5
You have to implement
func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool
for your UIButton
and check UITableView
bounds. Because hitTest
first checks if this tap point is within your view's bounds then calls hitTest
for your view. so you have to implement pointInside
and return true for your bounds.
WWDC for advanced scroll views and touch handling techniques wwdc
0 comments:
Post a Comment