Touch Events for UITableview - ios

I have a UIView overlaying a subclassed UITableview. The problem is that ,I cant get the tableview to scroll. I have tried overriding touchesBegan,touchesMoved,touchesEnded. I then tried to override hittest but that seemed to have no affect.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self];
NSLog(#"SMTable.touches began %#",NSStringFromCGPoint(touchPoint));
[super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self];
NSLog(#"SMTable.touches moved %# for :%p",NSStringFromCGPoint(touchPoint),touch.view);
[super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self];
NSLog(#"SMTable.touches ended %#",NSStringFromCGPoint(touchPoint));
[super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
[super touchesCancelled:touches withEvent:event];
}
- (UIView*) hitTest:(CGPoint)point withEvent:(UIEvent *)event {
//NSLog(#"SMTable.hitTest %#",NSStringFromCGPoint(point));
return [super hitTest:point withEvent:event];
}

If your UIView is above your UITableView, then all touch events will land in that UIView and your UITableView will not scroll. You need to disable interaction for your top most `UIView˜

When you need to create a specialized UITableView you are almost always better off using a UIViewController that contains a UITableView rather than mucking around in the UITableView hierarchy if at all possible. Apple are doing quite a bit of stuff in the tableview hierarchy which makes adding your own custom views to it often go awry. So, the short answer is : avoid inserting your own views into the tableView hierarchy.
In fact, I almost never use a UITableViewController subclass anymore. I always find myself needing to customize the view controller in a way that isn't easily supported from a UITableViewController-- such as creating a view to overlay the tableView as you are doing. Instead, create your controller like this:
#interface MyViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic,strong) IBOutlet UITableView *tableView
#end
If you are using Interface Builder, drop your tableView into the view controller's view and set the delegate and datasource to the view's owner. Or you can do the same thing in code via the viewDidLoad method. In either case, at this point you can treat the view controller exactly as if it were a UITableViewController with the added benefit of being able to do things like inserting views into self.view without things going horribly awry.

Related

add gesturerecognizer on top right side of navigation bar

I use the following code to add a gesture recognizer on the top right side of uinavigationbar but i get result if i tap anywhere on the navbar. How am i supposed to make a gesture for the top right corner?
- (void)handleGestureForTopRightBarButtonItem:(UIGestureRecognizer *)gestureRecognizer {
CGPoint p = [gestureRecognizer locationInView:self.navigationController.navigationBar];
if (CGRectContainsPoint(CGRectMake(self.navigationController.navigationController.view.width-20,0,100,self.navigationController.navigationController.view.height), p)) {
NSLog(#"got a tap on the right side in the region i care about");
} else {
NSLog(#"got a tap on the right side, but not where i need it");
}
}
As UIGestureRecognizer is reporting to a class object there are a couple of ways to solve this. UIGestureRecognizer was not meant to be stacked multiple times on the same view, if you do so you would very likely drain more Energy than you need apart from the loss of CPU power and lots of comparison code that has to distinguish all the running recognisers. But it would work..
a) write code that compares its coordinates and expected values and if they match in the range you want do your actions.
b) create another object that is living only in the coordinates you want and has it own UIGestureRecognizer. Not ideal, as written above.
c) use the power of UIControl which are also inherited UIView's that are also UIResponders.
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
- (void)endTrackingWithTouch:(nullable UITouch *)touch withEvent:(nullable UIEvent *)event; // touch is sometimes nil if cancelTracking calls through to this.
- (void)cancelTrackingWithEvent:(nullable UIEvent *)event;
d) use the power of UIView without a UIGestureRecognizer. Which by the way basically works also on CALayers, they do not have the sendAction methods, they are not UIControls.
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
BOOL inside = [super pointInside:point withEvent:event];
if (inside && event.type == UIEventTypeTouches) {
//[super sendActionsForControlEvents:UIControlEventTouchDown];
}
return inside;
}
e) code some Class that inherits from UIResponder, which basically is what UIControls do and use their API instead so you make use of touch coordinates as well.
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
[self someCoRoutineWithTouch:touch];
}
//[super touchesBegan:touches withEvent:event];
}
-(void)touchesMoved :(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
//do some stuff per touch
}
//[super touchesMoved:touches withEvent:event];
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
//do some stuff per touch
}
//[super touchesEnded:touches withEvent:event];
}
-(void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self touchesEnded:touches withEvent:event];
}
Just don't forget that UIGestureRecognizer is basically for gestures that are made of touches, even multiple touches but not mainly to catch touches in general, despite in a lot of examples they are used instead of coding a proper UIControl. Also dont forget in a Devices edges the recognition of gestures is limited by the nature of finger size.

Prevent parent view controller from receiving UITouch in child controller

So, in my app, then a map annotation is pressed, It reveals a modal view controller. Then when an element that modal controller is pressed, I open another, smaller controller. In both controllers, I have implemented the method
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self.view];
if (!CGRectContainsPoint([self.view viewWithTag:21].frame, touchPoint)) {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
Here is an image that shows my layout:
My problem is that when I touch in yellow circle one, the second modal dismisses, and after that, the first modal dismisses. SO, how can I prevent the first modal from receiving the touch.
PS: both modals are transparent UIViews, with smaller views inside them to display the content.
Obviously the event is passed through the view controller to the next responder.
In the class reference of UIResponder.
The default implementation of this method does nothing. However
immediate UIKit subclasses of UIResponder, particularly UIView,
forward the message up the responder chain. 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 touchesMoved: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.
So if you don't want the event to forward to the next responder, you should override the other methods.
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self.view];
if (!CGRectContainsPoint([self.view viewWithTag:21].frame, touchPoint)) {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}

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.

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

How to identify which event is passed to parent via nextResponder?

I have UITableViewCells each with a few UIImageViews. I want to pass the event to the parent UITableViewController so it can act upon it. How can I send information back to the UITableViewController to let it know which UIImageView triggered the event. Using the below code, it seems the UITableViewController touchesEnded is triggered when any of the children UITableViewCells fire an event. Any way to pass information in the UIEvent?
Is there a better way to go about event handling?
//UITableViewCell
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if ([touch view] == imageView)
{
[[self nextResponder] touchesEnded:touches withEvent:event];
}
}
//UITableViewController
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
NSLog(#"clicked");
}
For a quick and dirty way, you can use the objc_setAssociatedObject and objc_getAssociatedObject of ObjC runtime.
#import <objc/runtime.h>
set the object:
// static char key;
objc_setAssociatedObject(event, &key, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
and get the object:
id yourInterestObj = objc_getAssociatedObject(event, &key);
But I don't recommend this way, it may breaks the MVC. I think your should use the Responder Chain patter to handle this situation, check the documentation for -sendAction:to:from:forEvent: of UIApplication.

Resources