Stacking UITableViews does not pass touch events beneath its view - uitableview

I am having 3 UIViews stacked one on top of another
UITableview
planeView
rootView
TableView is at the top and rootView at the bottom. (rootView is not visible as TableView is on top of it)
I have implemented the following code in rootView
/*code in rootView*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {}
expecting that these functions will be called when the top most view ie TableView is touched or moved,but on the contrary none of the functions were called.
I also tried putting the following code in TableView so that the rootView methods are called
/*code in TableView so that the rootView methods are called(TableView is the subview of rootView)*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
[self.superview touchesBegan:touches withEvent:event];
}
As expected it did so but the problem is that the TableView delegates like
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
are not called.
Is there any way by which it can be ensured that the TableView delegates implemented in TableView class(didSelectRow:) and the touchesBegan:,touchesMoved.. functions in rootView are also called accordingly?
ie When i click on a TableCell both (didSelectRow:atIndex) function in--> TableView and (touchesBegan and touchesEnd) method in-->rootView are called.

In your subclass of UITableView you should have touch methods like this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.nextResponder touchesBegan:touches withEvent:event];
[super touchesBegan:touches withEvent:event];
}
The difference here is that you're passing the touch to the next responder instead of the superview, and you're doing this before passing the touch to super.
Then in planeView you need to pass touches like this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.superview touchesBegan:touches withEvent:event];
}
Keep in mind that this still may not work exactly as you expect. UITableView does a lot of mangling of the responder chain under the hood, in order to make it seem as if a UITableView (which is actually a complex collection of subviews) is just another view like a button or a label.

None of this worked for me
What solved it was simply:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
return view == self ? nil : view
}
Ref this article: https://medium.com/#nguyenminhphuc/how-to-pass-ui-events-through-views-in-ios-c1be9ab1626b

Related

how to stop touchesEnded: withEvent: propogate from subView to ViewController?

I have a view being added as a subView of a viewController.
Both this subview and viewController all implement this method
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
I found that when I tapped the subView, both these method will be called.
I want to call subview's touchesEnded only. How to achieve this nicely? (not to add a gesture in it)
In the touchesEnded, apple doc says this "If you override this method without calling super (a common use pattern), you must also override the other methods for handling touch events, if only as stub (empty) implementations."
what's the other method ?
You are close!
To prevent to pass touch event to superview, you should override all the methods for the touch events. Add all the touch event methods to your subview, then you should be OK.
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}
Instead of implementing the subview's touchesDidEnd why not do the whole work in the superview's touchesDidEnd something like this
if([touch anyObject].view == subview){
return;
}
This way you will be able to know whether the touch was originally from the subview or the superview.
Alternative : You can implement both the methods in the superview and subview, but like the above code you can return the call in the superview, if the view it interacted with is the subview, and keep working in the touches code in the subview
If in case you want a different opinion plz let us know what you are trying so that we can give the answer accordingly

Passthrough touches of 2 UIScrollViews

I have 2 descendants of UIScrollView
I have a UITableView which displays data
and i have a UICollectionView added above the UITableView
view
| - UITableView
| - UICollectionView
The UITableView can only scroll vertically and the UICollectionView can only scroll horizontally. I can only scroll my tableview where the collectionview isn't overlapping (which is off course expected behaviour) but i need to make it so that i can scroll my tableview even if i swipe vertically on my collectionview.
I cannot simply add the collectionview as a subview of the tableview because of other reasons (which i know, would make this work)
Is there any other possibility to let de touches from the collectionview passthrough to the tableview?
You can try to create a subclass of UICollectionView and add this code to your CustomCollectionView's .m file.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView == self) {
return nil;
} else {
return hitView;
}
}
As I understood, you want touches to be intercepted by UITableView as well as UICollectionView?
I think You can try resending touch events from your UICollectionView to UITableView.
(manually calling touchesBegin, touchesMoved, touchesEnded, etc.)
Maybe overriding touchesBegan, touchesMoved, touchesEnded methods will work for your case.
You can try overriding UICollectionView with your subclass (with property set to your UITableView instance) and implementing touch handling methods with something like this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
if (CGRectContainsPoint(self.tableView.frame, [touch locationInView:self.tableView.superview]) {
[self.tableView touchesBegan:touches withEvent:event];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
[self.tableView touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
[self.tableView touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
[self.tableView touchesCancelled:touches withEvent:event];
}
Hope it will help, however I'm not 100% sure about it.
I've found this article also, maybe it will be useful
http://atastypixel.com/blog/a-trick-for-capturing-all-touch-input-for-the-duration-of-a-touch/
You can add pan gesture recognizer with direction vertical on collectionview. On the vertical pan event, you can change the content offset of your table view to scroll it.

Programmatically initiate hitTest:WithEvent after gesture recognizer was canceled and return a different UIView to respond to events

I am overriding hitTest:withEvent to return self (the bottom most view)-
When returning self - my view will respond to touch events in turn initiating gesture recognizers.
If a gesture is canceled or some set of conditions happened - I want to manually initiate hitTest:withEvent and then return a different view to take care of the same sequence of events/touches that occurred. This is necessary as a gesture recognizer only initiates after hitTest:withEvent returns the gestures view and its state changed to began.
I am not sure how to do this - I thought about manually calling on my subviews
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
}
But I don't have the event parameter (The gesture received it)
I think this could not be done, pass touch event to UIGestureRecognizer is private API. But you can pass touch event the bottom most view received to any view you like and do your own gesture recognize.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UIView* selectView = [self _findMatchView];
// maybe convert touches to selectView coordinate
[selectView handleTouchBegan:touches withEvent:event];
}

why cant we detect a UITouch logic on UITableView, Objective C

I searched but not quite understand why we cant detect a UITouch on UITableView. What I am having right now is :a view controller with a table view located in its view. Please look at the picture below for your reference
In implementation class, I am enabling breakpoint for each UITouch methods which are
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
I notice that, these breakpoints are invoked if and only if you touch outside of the table view ( orange area )
I do not get it. I thought UITableView is subclass of UIScrollView which is subclass of UIView which is subclass of UIResponder. It means UITouch should be invoked. (correct me if I am wrong )
All comments are welcomed and appreciated here.
Rather than tampering with the table view, use a gesture recognizer. You can act as the delegate to ensure that all interactions work concurrently and enable and disable the gestures if / as required.
You can detect touches method in the UITableView by subclassing it as this:
I Test it and it print "Test" successfully
//TestTable.h
#import <UIKit/UIKit.h>
#interface TestTable : UITableView
#end
//TestTable.m
#import "TestTable.h"
#implementation TestTable
(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog("Test");
}
Tables utilize scroll views to handle panning, which use a pan gesture recognizer. Why not just tap into that?
CGPoint location = [self.tableView.panGestureRecognizer locationInView:self.tableView];
If you wish to detect the touches on UITableView, create a subclass of tableview and add implement UIResponder method, canBecomeFirstResponder.
#interface MyTableView: UITableView
#end
#implementation: MyTableView
- (BOOL) canBecomeFirstResponder{
return YES;
}
// then implement all other touch related methods, remember to call super in these methods
// such that it correctly forwards the events to other responders
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
}
#end

UIScrollView inside a UITableViewCell - No didSelect call

I have a tableviewCell, where the user can scroll horizontally. Since the scrollView covers nearly the whole cell, the tableView method didSelectRow gets not called if the user clicks the cell.
So I thought, I could pass the touch event of the UIScrollView to the cell, but still the didSelectRow doesnt gets called.
I subclassed UIScrollView to pass the touch event only, if the touch was not a drag:
- (void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
{
NSLog(#"touch scroll");
// If not dragging, send event to next responder
if (!self.dragging)
[self.superview touchesEnded: touches withEvent:event];
else
[super touchesEnded: touches withEvent: event];
}
Any ideas on how to pass the click to the table, to get the delegate-methods called and keep the scrolling inside the scrollview?
You can actually do this without subclassing UIScrollView. Whether you have a custom cell, or are setting properties in cellForRowAtIndexPath in the UITableView, you can do the following:
[cell.contentView addSubview:yourScrollView];
yourScrollView.userInteractionEnabled = NO;
[cell.contentView addGestureRecognizer:yourScrollView.panGestureRecognizer];
The reason you can do this is because scrollView has its own panGestureRecognizer that's accessible to the programmer. So, just adding it to the cell's view will trigger the scrollview's gesture delegates.
The only drawback of this approach is that subviews of the scroll view are unable to receive any touch input. If you need this you will have to chose a different approach.
I just encountered the same problem.
In your subclass make sure to include the full set of methods:
-(void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.dragging)
[self.superview touchesCancelled: touches withEvent:event];
else
[super touchesCancelled: touches withEvent: event];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.dragging)
[self.superview touchesMoved: touches withEvent:event];
else
[super touchesMoved: touches withEvent: event];
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.dragging)
[self.superview touchesBegan: touches withEvent:event];
else
[super touchesBegan: touches withEvent: event];
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.dragging)
[self.superview touchesEnded: touches withEvent:event];
else
[super touchesEnded: touches withEvent: event];
}
The selected answer is correct, but I updated the code based on a bug I was getting.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.dragging) {
[super touchesMoved:touches withEvent:event];
} else {
if ([self.delegate isKindOfClass:[UITableViewCell class]]) {
[(UITableViewCell *)self.delegate touchesCancelled:touches withEvent:event];
}
[self.superview touchesMoved:touches withEvent:event];
}
}
If your self.delegate is not the UITableViewCell, than replace that property with a property to your cell.
The cell needs to retrieve the cancel touch event during movement to prevent the undesired results. It can be easily reproducible as follows.
Highlight the cell (assuming the scroll view is over the whole cell, if not highlight the scroll view)
While the cell is highlighted, drag the table view
Select any other cell and now the previously highlighted cell will retrieve the didSelectCell state
Another point to mention is that order matters! If the self.delegate is not called before the self.superview then the highlighted state wont happen.
Swift 3
scrollView.isUserInteractionEnabled = false
contentView.addGestureRecognizer(scrollView.panGestureRecognizer)
try set this
_scrollView.canCancelContentTouches = NO
also, it is bad to partially forward touch events

Resources