UITableView single clicks - objective-c

I have a UITableView and UINavigationController, and I'd like to distinguish between two clicks: 1) normal click that selects a row and 2) a click that happens ANYWHERE else on the screen (other than the buttons on the UINavigationController). I wrote this code:
singleTap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(singleTapHandler:)];
singleTap.numberOfTapsRequired = 1;
The problem with this however is that it overrides the normal clicks that select a row.

I assume you're putting the tap recognizer on either the UIWindow itself, or the window's sole subview. You need to give the tap recognizer a delegate, and that delegate needs to implement gestureRecognizer:shouldReceiveTouch:.
In that method, you want to return NO if the touch is in a button or if the touch is in a table view cell, and YES otherwise. You need to walk up the view hierarchy, starting with the view that the touch landed in, looking for either of those classes.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
for (UIView *view = touch.view; view; view = view.superview) {
if ([view isKindOfClass:[UIButton class]])
return NO;
if ([view isKindOfClass:[UITableViewCell class]])
return NO;
}
return YES;
}

Related

check if a uiView has handled touch

What I want to do is put a UIView on top of another UIView, and both of them are screen size. The top UIView includes lots cocos nodes and will respond when I touch them. But when I touch a place that has no cocos node, the bottom UIView should respond.
I don't know how to do this. My imagine is check if top uiView is handled touch, do nothing. other wise let the bottom UIView start respond. But I don't know how to check that. I only know how to check touch but it seems the UIView will also be touched when I touch some place it can't handle.
I think you not requires 2 views.
Just take one view only and add all your node to that view.
in .h
IBOutlet UIView *bgView;//your view
UITapGestureRecognizer *viewTapRecognizer;// view tap recognizer
in .m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
viewTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handelGesture:)];
[bgView addGestureRecognizer:viewTapRecognizer];
for (UIView *subView in [bgView subviews]) {
UITapGestureRecognizer *nodeTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handelGesture:)];
[subView addGestureRecognizer:nodeTapRecognizer];
}
}
- (void)handelGesture:(UITapGestureRecognizer*)sender {
if (sender == viewTapRecognizer) {
// your view is tapped
NSLog(#"........Tapped view..........");
}
else {
// your node is tapped
NSLog(#"........Tapped node..........");
}
}
Try this. it might work for you.
You can achieve it by 2 ways: using pointInside... method or hitTest... method.
If you will use pointInside... you can use only your views (the top view and the bottom view). You will subclass UIView with the top view and override pointInside... method. You will return YES if user's tapped a cocoa node else returns NO.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
BOOL inside = [self didUserTapCocoaNode:point];
return inside;
}
If you want more complex logic, you should use third view, subclass of UIView (the container view), that will be contain the top view and the bottom view. Override method hitTest..., and return the bottom or the top view.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = nil;
if ([self touchedCocoaNode:point])
{
result = self.topView;
}
else
{
result = self.bottomView;
}
return result;
}
UPD
Example implementation of didUserTapCocoaNode with pseudo code:
- (BOOL)didUserTapCocoaNode:(CGPoint)pointInSelf
{
__block BOOL tappedSomeNode = NO;
[self.nodes enumerateObjectsUsingBlock:^(NodeType* obj, NSInteger idx, BOOL* stop){
CGPoint pointInNode = [obj convertPoint:pointInSelf fromView:self];
tappedSomeNode = [obj pointInside:pointInNode withEvent:nil];
*stop = tappedSomeNode;
}]
return tappedSomeNode;
}

How can a UIViewController inside a UIPageController get touch events?

It seems that the button simply doesn't get pressed.
I tried:
UIPageViewController Gesture recognizers
However, here is the catch
if page transition of UIPageViewController is UIPageViewControllerTransitionStyleScroll then
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
NSLog(#"%#",self.pageViewController.gestureRecognizers.description);
NSLog(#"%#",self.pageViewController.gestureRecognizers.description);
will simply be empty.
I tried adding my own gestureRecoqnizers instead of button into the UIViewControllers's subview. It still doesn't work.
So I have a UIPageViewController that display a UIViewController whose view has some button. How do I get that button pressed?
I also tried both solutions here:
http://emlyn.net/posts/stopping-gesture-recognizers-in-their-tracks
None works. First of all I cannot disable gesture recoqnizers of the UIPageViewController because there is no such thing. In scrollView mode, UIPageViewController doesn't have gesture recoqnizers.
I tried to add my own gesture recoqnizers to my own button but those are never called.
I want the UIPageViewController to still handle swipe though. But not tap.
I can't access the gesture recoqnizers of UIPageViewControllers because self.pageViewController.gestureRecognizers is empty. The UIPageViewController seems to just "absorb" all tap events. So my button doesn't get it.
I can add button in front of UIPageViewController but that button will absorb the swiping action which I want UIPageVIewController to still handle.
By the way if we uses the template from IOS (Create a page based app, and change the transition to scroll the pageviewcontroller.gesturerecoqnizers will be empty
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Configure the page view controller and add it as a child view controller.
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];//Turn this to scroll view
self.pageViewController.delegate = self;
SDDataViewController *startingViewController = [self.modelController viewControllerAtIndex:0 storyboard:self.storyboard];
NSArray *viewControllers = #[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:NULL];
self.pageViewController.dataSource = self.modelController;
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
// Set the page view controller's bounds using an inset rect so that self's view is visible around the edges of the pages.
CGRect pageViewRect = self.view.bounds;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
pageViewRect = CGRectInset(pageViewRect, 40.0, 40.0);
}
self.pageViewController.view.frame = pageViewRect;
[self.pageViewController didMoveToParentViewController:self];
// Add the page view controller's gesture recognizers to the book view controller's view so that the gestures are started more easily.
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
NSLog(#"%#",self.pageViewController.gestureRecognizers.description); //This is empty
while (false);
}
Maybe you can set up a custom gesture recognizer in your UIPageViewController. This way, you will be able to filter through UITouch Events, by implementing this in your gesture recognizer delegate (the one for your UIPageViewController) :
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gesture shouldReceiveTouch:(UITouch *)touch {
if (touch.tapCount > 1) { // It will ignore touch with less than two finger
return YES;
}
return NO;
}
This, you will tell the UIPageViewController gestureRecognizer to not care about touch with one finger. You'll have to try it, but i think it might do the trick. The problem here is you have to ask your user for a two-finger swipe...
Check here for an other answer

UISlider inside UIPageViewController

I have a PageViewController which is initialized like this:
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
On one of the pages, there's a UISlider.
My problem is that when I have transitionstyle set to UIPageViewControllerTransitionStyleScroll, it takes 150-200 ms before beginTrackingWithTouch is invoked on the slider.
This behavior is not seen when I use UIPageViewControllerTransitionStylePageCurl, where the UISlider is selected instantly.
This means that unless the user waits a bit before dragging the slider (a video progress), the page will turn instead, which is far from ideal.
The Page curl animation does not meet the demands of the app, so any explanation or workaround is appreciated.
Since with UIPageViewControllerTransitionStyleScroll gesture recognizers isn't available, you can use this:
for (UIView *view in pageViewController.view.subviews) {
if ([view isKindOfClass:[UIScrollView class]]) {
UIScrollView *scrollView = (UIScrollView *)view;
scrollView.delaysContentTouches = NO;
}
}
I solved this issue by add a pan gesture on UISlider and set:
self.sliderGesture.cancelsTouchesInView = NO; // make touch always triggered
and implement delegate method like:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return otherGestureRecognizer.view.superview == self.parentViewController.view;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// only receive touch in slider
CGPoint touchLocation = [touch locationInView:self.view];
return CGRectContainsPoint(self.slider.frame, touchLocation);
}
You can try to set the delegate of the page view controller gestures to the root view controller:
for (UIGestureRecognizer* gestureRecognizer in self.pageViewController.gestureRecognizers) {
gestureRecognizer.delegate = self;
}
And then prevent the touch of the gestures if it appears inside UISlider which is a subclass of UIControl:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return ([touch.view isKindOfClass:[UIControl class]] == NO);
}
What helped me was to add pan-gesture-recognizer to UIView which holds UISlider, so in the end I have
UIPageViewController->UIScrollView->...->MyView->UISlider
The 'MyView' thing had pan gesture registered to it which did nothing, but served just to NOT propagate events to scroll view.

Handling Multiple Gestures on a View

Is there any way to detect a single tap vs. a scrolling gesture on a UIWebView?
I have a UIWebView, that contains rich text that many times, scrolls, if there is a lot of text content. I need to add a new feature that will allow the user to tap the UIWebView to get different content.
The problem is, my solution for this was to place a clear custom button on top of the UIWebView, which handles the tap but kills the scrolling feature. How do the cool kids do this type of thing?
Thanks
I solved this with a gesture recognizer. This eliminates the need for the UIButton overlay altogether.
//Handle taps on the UIWebView
self.singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(**tapDetected:**)];
singleTap.numberOfTapsRequired = 1;
singleTap.delegate = self;
[self.readAboutItView addGestureRecognizer:singleTap];
//Set up the event handler
- (IBAction)**tapDetected:**(UIGestureRecognizer *)sender {
//Do something with the content
[self webViewTouched:self];
}
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
return YES;
}
-(BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}

How do I assign a view as the first responder, after touching a tableview cell?

Here's a more info on my sitation...I have a tableview that when you touch a cell within the tableview, it adds a custom view called DraggableView to the window on the exact same position as the tableview row you selected, and the same size.
EDIT: Here's how I added the DraggableView subview to the view. This code is on the custom tableviewcell class:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UIView *topmostView = [[[super superview] superview] superview];
UITableView *parentTableView = (UITableView *)[self superview];
CGPoint draggablePoint = parentTableView.frame.origin;
CGSize draggableSize = parentTableView.frame.size;
draggablePoint.y += indexPath.row * 44;
dragView.frame = CGRectMake(draggablePoint.x, draggablePoint.y, draggableSize.width, 44);
[topmostView addSubview:dragView];
}
This draggable view is made so it is draggable. On it's designated initializer, it adds a UIPanGestureRecognizer to the view so you can pan it around. I do this like so:
- (void) activatePan {
UIPanGestureRecognizer *pan =
[[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panMove:)] autorelease];
[self addGestureRecognizer:pan];
}
- (void) panMove:(UIPanGestureRecognizer*)recognizer {
CGPoint touchLocation;
if (recognizer.state == UIGestureRecognizerStateBegan) {
currentFrame = self.frame;
touchLocation = [recognizer locationInView:self];
}
CGPoint translatedPoint = [(UIPanGestureRecognizer*)recognizer translationInView:self];
//The object is being moved across the screen
self.frame = CGRectMake(currentFrame.origin.x + translatedPoint.x, currentFrame.origin.y + translatedPoint.y, currentFrame.size.width, currentFrame.size.height);
}
What I need is for this newly created Draggable View to have the user's "focus" when his finger is still on top of the row. This is for the draggable view to be draggable once it's created.
What happens as of now, is that the draggable view is created, but I have to release my finger, and tap the draggable view to pan it around. I wish to skip the step of releasing the finger and touch the view, so I can drag it once it's been created. Any ideas?
From the Apple documentation:
- (BOOL)becomeFirstResponder
"You may call this method to make a responder object such as a view the first responder. However, you should only call it on that view if it is part of a view hierarchy. If the view’s window property holds a UIWindow object, it has been installed in a view hierarchy; if it returns nil, the view is detached from any hierarchy."