I am writing a board game app (like chess). The main view recognizes swipe gestures (UISwipeGestureRecognizer) started anywhere in its fullscreen view, which make the board rotating.
Now I added a square-shaped transparent subview exactly over the board. This is an UIControl subclass that detects touches - as moves of pawns around the board:
[self.view addSubview:self.boardControl]
I expected that swipe gestures will be blocked at the area of screen that is covered with UIControl subclass. And they are not. So when I make a quick touch and drag (a swipe) over my square boardControl, it first is detected as a pawn move, but then is detected again as swipe which rotates the board.
How can I make my UIControl subclass to block a flow of touch events started in its frame to its superviews?
I can prevent my app from reacting to swipe gestures, by filtering touches at the level of superview, either by:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint location = [gestureRecognizer locationInView:self.view];
CGRect frame = self.boardControl.frame;
if ( CGRectContainsPoint(frame, location) )
return NO;
return YES;
}
or by:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch
{
CGPoint location = [touch locationInView:self.view];
CGRect frame = self.boardControl.frame;
if (CGRectContainsPoint(frame, location))
return NO;
return YES;
}
but I want to solve that issue one level earlier: I want boardControl to not pass up any of started within their frame touch events, higher in views hierarchy.
Can UIControl subclass "cover" its superview, and "eat" all touches it gets, so the superview will not need to access its frame to guess if such a touch has to be filtered out or not?
All you need is implement UIGestureRecognizerDelegate is provides way to make requested things.
I think you should start from gestureRecognizer:shouldReceiveTouch: examples
Related
I have made a graph with data in a UIView called HeartrateGraph. In a UIViewController named HRGraphInfo, I have a connected label that should output values when the graph is touched. The problem is, I don't know how to send a touched event using delegates from the UIView to the UIViewController.
Here is my touch assignment code in the UIView:
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
for (int i = 0; i < kNumberOfPoints; i++)
{
if (CGRectContainsPoint(touchAreas[i], point))
{
graphInfoRF.heartRateGraphString = [NSString stringWithFormat:#"Heart Rate reading #%d at %# bpm",i+1, dataArray[i]];
graphInfoRF.touched = YES;
break;
}
}
This segment of code is in a touchesBegan and properly stores the data value and number in the object graphInfoRF (I just did not show the declarations of dataArray, kNumberOfPoints, etc).
I am able to access graphInfoRF in the UIViewController using:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (graphInfoRF.touched == YES) {
self.heartRateLabel.text = graphInfoRF.heartRateGraphString;
}
else {
self.heartRateLabel.text = #"No data got over to this file";}
}
The label will show the correct string, but only after the data point on the graph is touched AND the label is touched right after. How do I change the touchesBegan so that once I touch the data point on the graph it will fill the label with the data automatically without the need for a second and separate touch on the label?
All ViewControllers comes with a single view it manages once it's initialized. You should be familiar with this view, you see it whenever you use a ViewController in the Interface Builder and you can access it using self.view if you're modifying a subclass.
Since ViewControllers come with a view, it also receives touch events for that view. Implementing touchesBegan in the ViewController will then receive events for that view, and normally any subviews that view is managing. Since you've down your own implementation of 'touchesBegan' in your HeartRateGraph, and since HeartRateGraph is a subview of ViewControllers main view, HeartRateGraph will receive and handle the touch event first before the ViewController ever has a chance to receive and handle the event like it normally would (think of bubbling up).
So what's happening is, the code to change the label in ViewController is only called when the label is touched because the label is a subview of the ViewController's main view... and also label doesn't have its own touches implementation, so ViewController and is able to retrieve and handle the event the way you want only when you click somewhere outside the graph. If you understand then there are two ways to solve this.
Either pass the event up to your superview
[self.superview touchesBegan:touches withEvent:eventargs];
or the proper recommended way of doing it:
Protocols and Delegates where your View makes a delegate call to it ViewController letting it know the graph has been touched and the ViewController needs to update its contents
I have an iPad project structured with a UISplitViewController:
RootViewController
DetailviewController
Both of them are detecting touches with Gesture Recognizer inside their own Class.
I would like to create a transparent UIView on top of all the Classes to detect ONLY a Diagonal Swipe (from the left bottom corner to the right top corner).
So, when the swipe will be detected I will launch a function otherwise nothing appended and the touch should be passed on the low level view.
I tried these two solutions:
Add a GestureRecognizer on this top transparent view but this will hide all touches to the lower hierarchy views.( with userInteraction enabled: YES ofcourse);
The other solution is to make the init like this
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[self setBackgroundColor:[UIColor colorWithWhite:1 alpha:0.01]];
[self setUserInteractionEnabled:NO];
}
return self;
}
and try to detect the swipe with
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
But at this point all the touches are not detected.
Anybody have a nice solution?
I will not create a transparent UIView like you are mentioning. I will add a UISwipeGestureRecognizer to the UISplitViewController's view this is already the view that contains all your subviews. You can have access to the view within the app delegate:
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
// attach the swipe gesture to the view that embeds the rootView and the detailView
UISwipeGestureRecognizer* swipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:splitViewController.view action:#selector(swipeUpdated:)];
Can't you just add a gesture recognizer to the UISplitViewController's view?
You should look into Container Controllers. You can make your own SplitViewController and make a third view on top of the controller that detects the swipe. Custom container controllers are pretty straight forward and gives you a lot of flexibility.
I want to make a view that is initially invisible on the left side of the screen. When the finger pan from the very left side of the screen, the left appears and follow the finger. I mean exactly like the Notification Center in iOS 5, but on the left side...
Here is a picture of what I want : http://i.imgur.com/Bb6tC.png
My problems is that there is a scrollview on the view underneath and the PanGestures are interfering...
I tried to catch touches in the underneath view only on a defined zone like this :
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
UIViewController *touchedVC = (UIViewController *)[gestureRecognizer.view nextResponder];
CGPoint point = [touch locationInView:touchedVC.view]
if (point.x < SIDE_VIEWS_HANDLE_SIZE)) {
return YES;
}
I also used the delegate method
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
But still, I can't get all these pan gestures not to interfere with each other... Any idea please ? :-)
Checkout ECSlidingViewController. They have already done the heavily lifting for this type of view.
I also had to subclass UIScrollView to catch its UIGestureRecognizer delegates.
I have a UIScrollview with paging enabled. There are 3 views (pages) inside this scroll view. There's a tap gesture on the parent view of the scrollview that shows and hides a navigation bar at the top.
The Problem:
In one of the pages I want to add buttons. But the problem is that whenever i tap these buttons, the show/hide navigation bar method is also fired. What is the best way to pass the touch only to these buttons and not the the parent view of the scrollview?
NJones is on the right track, but I think there are some problems with his answer.
I assume that you want to pass through touches on any button in your scroll view. In your gesture recognizer's delegate, implement gestureRecognizer:shouldReceiveTouch: like this:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)recognizer shouldReceiveTouch:(UITouch *)touch {
UIView *gestureView = recognizer.view;
// gestureView is the view that the recognizer is attached to - should be the scroll view
CGPoint point = [touch locationInView:gestureView];
UIView *touchedView = [gestureView hitTest:point withEvent:nil];
// touchedView is the deepest descendant of gestureView that contains point
// Block the recognizer if touchedView is a UIButton, or a descendant of a UIButton
while (touchedView && touchedView != gestureView) {
if ([touchedView isKindOfClass:[UIButton class]])
return NO;
touchedView = touchedView.superview;
}
return YES;
}
I have a custom UIView class. Inside that view, there is an image view. (I'm making a UISlider from scratch).
I am trying to get the image view -- the thumb of the slider -- to move across the view. Below is the code from the UIView class
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchPoint = [touch locationInView:self];
if ( CGRectContainsPoint(self.thumb.frame, touchPoint))
self.thumb.center = CGPointMake(touchPoint.x, self.thumb.center.y);
return YES;
}
When I place my finger on the thumb of the slider and try to drag it, nothing happens. However, when I touch outside the thumb of the slider, drag my finger to the thumb without letting go, and then try dragging the thumb, it works fine.
How can I modify my code so that the method will be called when someone holds the thumb and tries to drag it?
My guess is that you have user interaction enabled on your image view, which is in turn not relaying the touch events to the backing logic view. Do something like this:
self.thumb.userInteractionEnabled = NO
Put that in whatever init method you are using and the thumb view will not interrupt the touch events anymore.