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.
Related
I have some custom animations in my UIButton subclass. I override some inner methods for starting and ending animation
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
//there is my start animation
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
//there is my end animation
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
//there is my end animation
}
There is almost all good. All my buttons make some actions on event UIControlEventTouchUpInside. And there is my problem. Methods touchesEnded and touchesCancelled are called only after my app handle UIControlEventTouchUpInside event. So animations in button looks not so nice like I want.
So question is: can I delay UIControlEventTouchUpInside event on some ms and firstly handle touchesEnded method of my button for smooth animation?
I am having UIView with touches begin/moved/ended methods.I have added UIScrollView as a subview for the UIView.Now I am not receiving any touch events in those touch corresponding methods after adding UIScrollView as a subview.I tried setting the UIScrollView properties canCancelContentTouches, delaysContentTouches to NO.But still it is not working.
You can override UIView methods:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
Determine if the touch is within the bounds of subview (scrollview) and pass them to your parent view
return [super hitTest:point withEvent:event];
and
return [super pointInside:point withEvent:event];
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
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
I have followed this great tutorial and I finally managed to implement a 3 independent rows scrollable interface.
I am left with a problem though, as the key of that tutorial is the use of method:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
NSLog(#"in hitTest");
if ([self pointInside:point withEvent:event]) {
return _scrollView;
}
return nil;
}
in order to handle the scrolling even when outside the scrollview area.
In fact my rows are filled with UIButtons and their TouchUpInside events got mixed up with hit events. Is there a way to make this method recognize those events and reject them, letting them propagate to legitimate delegate?
You should probably implement the -hitTest:withEvent: method as follows:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *superView = [super hitTest:point withEvent:event];
if (superView == self)
return _scrollView;
return superView;
}
This will allow interaction within subviews of the UIScrollView.