self.nextResponder vs super for touchesMoved - ios

Here's the setup.
I have a custom UIScrollView as a subview of PrimeView.
The custom UIScrollView can be regarded as overlaid scrollview and it gets all touch events initially.
Now, when the scroll view isn't dragging, I want it to pass touch events to other responders.
Below is my code at present, but I'm not sure about the difference between self.nextResponder and super here.
I don't get why touchesBegan is passed to the superview correctly, but touchesMoved isn't passed to the superview.
-(void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
if (!self.dragging)
{
[self.nextResponder touchesBegan: touches withEvent:event];
}
[super touchesBegan: touches withEvent: event];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.nextResponder touchesBegan: touches withEvent:event]; //
[super touchesMoved:touches withEvent:event];
}

Apple's stance is that you should never do something like this:
[self.nextResponder touchesBegan: touches withEvent:event];
If you do this, you're going outside UIKit's own forwarding of messages up the responder chain, and the result is undefined.
Also, in your touchesMoved:withEvent:, you're sending touchesBegan:withEvent: to nextResponder, which seems suspicious.
Also, in your touchesMoved:withEvent:, you're passing on the event twice, which seems like a bad idea.
If you want to handle drags conditionally, your best bet is to use a UIPanGestureRecognizer. If the recognizer doesn't accept the event, it will be forwarded up the responder chain normally.
If you are a registered (paid) iOS developer, you should have access to the WWDC 2012 videos. I strongly recommend you watch the “Enhancing User Experience with Scroll Views” video. I won't say more about it because its contents are still under NDA.

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

touchesBegan not called on UICollectionView subclass

I have subclassed UICollectionView as CoolGridCollectionView and I have overrides the touchesBegan: and touchesEnded: methods. But those are not getting called, when I tap the cell. And that cells are having single and double tap gesture recognisers. I need the position, where the tap has happened. I can get the position by using covertPoint: something else. But Why the touches methods are not called? Does iOS handles touches of UICollectionView specifically.
I’m not 100% sure, but you can try hitTest:withEvent: method instead of -touchesBegan:withEvent: method. Hope this will helpful to you.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
touchedView = [super hitTest:point withEvent:event];
NSSet* touches = [event allTouches];
// handle touches if you need
return touchedView;
}

Is there any way i can register a touch down for UISegmentedControl

I have a segmented control,with 5 segments..I want some event to be fired when a touch down happens on any of the segment and i mean tap and hold,without lifting the finger.
And when the user lifts his finger,it should reset
I have tried using touchesBegan and touchesEnded but i don't get the current selectedIndex in touchesBegan,here's my code
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
long oldValue = self.selectedSegmentIndex;
[super touchesBegan:touches withEvent:event];
if (oldValue == self.selectedSegmentIndex )
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"touchesEnded");
NSLog(#"selectedIndex%lu",self.selectedSegmentIndex);
[super touchesEnded:touches withEvent:event];
self.selectedSegmentIndex = -1;
}
Is there any way to achieve a touch down event effect for a segmentedControl,or any other alternative?
You can use touchDown: IBAction selector.
You can try to override:
- (void) setSelectedSegmentIndex:(NSInteger)toValue
Quite frankly, I do not think it is possible to do with UISegmentedControl. You do not get selected segment index until touchesEnded:withEvent: is called.
If you want to get in touchesBegan:withEvent:, my advise would be to write your own custom view that look like UISegmentedControl and gives you call back on touchesBegan:withEvent:.
As suggested by kientux above, adding transparent views or buttons on top of each segment in UISegmentedControl could be another potential solution to your problem but it would be very very tricky!
Good luck!

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

iPhone: touches on UIViewController

I have a UIViewController, on top I put an UIImageView, then a UIScrollView.
I can process touches on the UIScrollView just fine, however I want to process certain touches on the UIViewController.
I only need touches on the UIScrollView that are held for 5 seconds (This part works fine). Anything less than that I want to pass to the UIViewController.
On the UISCrollView I call this:
- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
// some custom code to process a specific touch.
// all other touches
[super touchesBegan: touches withEvent: event];
}
Is there a way to pass touches from the UIScrollView to the bottom UIViewController, or do I have to pass a references to the UIViewController into the UIScrollView?
Solved!
Needed to add:
[self.nextResponder touchesEnded: touches withEvent:event];
to the top most controller.
Thank you to myself!
touchesBegan description in the Apple Documentation tells:
To forward the message to the next responder, send the message to super (the superclass implementation); do not send the message directly to the next responder. For example,
[super touchesBegan:touches withEvent:event];
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.

Resources