Receiving touch events on more then one UIView simultaneously - objective-c

I have a bunch of UIViews stacked one upon the other(not nested). I want them all to react to touch, but it seems that the topmost view obscures the views beneath it, preventing them from receiving touch events.
At first i thought i’d catch all touch events with the topmost view, and then manually call
hitTest, or pointInside methods on all the underlying views, but i found out that both methods are somehow private(could it be?) and cannot be accessed.
Any ideas how to pull it off?

You can check if the touch is for your topmost view. If it doesn't you can call the same method of your superview. Something like [self.superview sameMethod:sameParameter].
Your topmost view has a method
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
Inside that method you are doing your logic right?
Inside the method can't you check if the touch received is at your topmost view with
UITouch *touch = [touches anyObject];
[touch locationInView:self];
And if it doesn't you pass it to the superView's same method using
[self.superview touchesEnded:touches withEvent:event];

Touches are sent to a single view. That view can then optionally pass them up the responder chain. If you want to handle touches to a collection of views you should have them forward those events up to the next responder and have a common parent of all of them (or their view controller since the controller is also part of the responder chain) handle those touches.
https://developer.apple.com/library/mac/documentation/General/Devpedia-CocoaApp-MOSX/Responder.html

Related

How to use touchesBegan from one UIView in another UIViewController

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

Adding touch-area to UIView (iOS5)

I have a MainView with MainViewController. Now I want to add a specific area where I want to register touches (painting in a specific area). How could I do this?
I thought about adding a sub-view with its own sub-viewcontroller, but this guy tells this is not a good approach.
The post you linked to is partially out of date because it was written before Apple introduced support for View Controller Containment in iOS 5.
That said, it's your choice whether:
the subview is managed by its own view controller or
you use the MainViewController directly to respond to touches in the subview or
you create a UIView subclass that interprets touches on itself without the help of a view controller.
Add a custom view as a property, called touchArea
-(void) touchesBegan/Moved/Ended (NSSet *)touches withEvent:(UIEvent *)event {
CGPoint location = [[touches anyObject] locationInView:self.view];
if (CGRectContainsPoint(touchArea.frame, location))
//code
}
}

Affecting UIViewController from a child UIView

My question might sound rather strange, but still I didn't find any reference and I really need help.
so my point is, I have a ViewController which has a collection of UIViews, to be specific a subclass of UIView with
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
overridden
from this event i want to load a specific data (according on the UIView touched) on the UIViewController's child UIWebView
Is there a way to address parent ViewControllers children from the UIView that receives the action
Or am I looking into a completely wrong direction.
Any hel will be greatly appreciated, thanks in advance.
You can use delegate for example:
#protocol someProtocol
- (void)recivedTouch:(UITouch *)touch fromUIView:(UIView *)uiView;
in you view:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
[self.delegate recivedTouch:touch fromUIView:self];
}
View controller implemed method from delegate:
- (void)recivedTouch:(UITouch *)touch fromUIView:(UIView *)uiView
{
if (uiView == self.someView)
{
// do some stuff with uiWebView
}
}
try to look at it differently. Your touchesBegan shouldn't do anything which is not connected with the view. Your view is just a UI component, it shouldn't control application flow (data loading). That's the job of the view controller.
Look how UIButton is implemented. The button does not do anything when clicked. The controller has to tell it "this is the method that should be called when the user clicks you"
There are two ways how to tell the view what it should do for a specific action - creating a delegate or passing a selector that should be called.
Consider also extending UIControl, which has most of the actions already implemented.
Alternatively use NSNotificationCenter: Send and receive messages through NSNotificationCenter in Objective-C?
Send a message from your view, hook the message from your controller. Notificationas can get a bit messy though, the delegate is probably a cleaner solution

UIScrollView decide between drag and tap

I am trying to determine a Single Tab on a View inside of a UIScrollView. The Problem is that The UIScrollview catches all the gestures.
What I tried so far:
I override the following method in my UIScrollView:
-(BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view
{
UITouch *touch = [touches anyObject];
if([touch tapCount]== 2) return YES;
return NO;
}
This works fine, I can now reach the UITapGestureRecognize on my UIView, unfortunately I can only detect double-taps because the [touch tapCount] == 1 is always beeing called (dragging or zooming in the UIScrollView). But actually the UIScrollview does not need the "Single-Tap-Function"
Is there a way to decide between a drag (Scroll or zoom) and a single Tap inside this method? I cant find it..
-(BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view
Thanks in advance
Fabi
It sounds like you only want the tap recognizer to succeed if the touch doesn't scroll the scroll view. This is pretty easy because the scroll view uses a pan gesture recognizer for scrolling. On iOS 5, you can just do this:
[self.tapRecognizer requireGestureRecognizerToFail:self.scrollView.panGestureRecognizer];
If you want to support older versions of iOS, you have to do this:
for (UIGestureRecognizer *recognizer in self.scrollView.gestureRecognizers) {
if ([recognizer isKindOfClass:[UIPanGestureRecognizer class]])
[self.tapRecognizer requireGestureRecognizerToFail:recognizer];
touchesShouldBegin: is a delegate method on UIView. It is not part of a UIGestureRecognizer.
You'll need to create a UITapGestureRecognizer, set the number of taps you want to trigger the recognizer and add it to your view.
Here is the documentation for UITapGestureRecognizer
I'm assuming you meant "single tap"--your scrollView belongs to the UIView tied to your controller. Assign the tap gesture recognizer to the that UIView. It gets first picks on all gestures and then passes down the ones it isn't interested in.

Drag event in subview of UIScrollView

How can I add a drag event to a subview of a UIScrollView? The structure is the following:
-UIView
-UIScrollView
-UIView
-UIView
...
I tried to start with the following:
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *aTouch = [touches anyObject];
CGPoint location = [aTouch locationInView:self.superview.superview];
[UIView beginAnimations:#"Dragging A DraggableView" context:nil];
self.frame = CGRectMake(location.x, location.y,
self.frame.size.width, self.frame.size.height);
[UIView commitAnimations];
}
But nothing happens! Help would be very much appreciated.
Thanks
Just incase anyone finds this question like I did, I solved this problem by adding a gesture recognizer to the subviews in the scrollview. The gesture recognizer will handle the touch event itself.
Bruno, one possibility is to use the gesture recognizers, as Scott mentions.
Another possibility is to use the touchesMoved: method you mention. Using the touchesMoved: method requires you to implement another three methods, touchesBegan:, touchesEnded:, touchesCancelled:. They cover the phases of the finger touching the screen:
First contact (touchesBegan) allows you to set up variables as needed.
Continuous move (touchesMoved) allows you to track the move and move your content continuously.
And finally removing the finger (touchesEnded) where you can finalize any changes you want to keep.
touchesCancelled is required for clean-up if the gesture is interrupted (by phone call, or some other controller grabbing the touch event).
You need all four methods on the same class, otherwise a superclass implementing the method will grab the touch processing and your code won't run as expected.
Regards, nobi